Première Analyse Systémique
L’analyse systémique porte sur la performance des modèles en utilisant leurs paramètres par défaut, en couvrant si possible plusieurs différent types de modèles :
| Linéaire |
Régression logistique (on utilisera le modèle linéaire généralisé) |
xgboost (C++), H2O (Java) |
| Non-linéaire sans ensemble |
Arbre de décision (on utilisera une itération de boosting d’arbres de décision) |
xgboost (C++), H2O (Java) |
| Non-linéaire d’underfitting |
Arbre de décision (on utilisera le modèle de Random Forest) |
xgboost (C++), H2O (Java) |
| Non-linéaire d’overfitting |
Arbre de décision (on utilisera le modèle d’arbres de décision boosté) |
xgboost (C++), H2O (Java) |
| Linéaire d’overfitting |
Réseaux de neurones (on testera une architecture 32x6 + ReLU / Tanh, 16x16x6 + ReLU / Tanh) |
H2O (Java) |
Par simplicité, on utilisera xgboost, qui est un modèle codé en C++ dont la vitesse de calcul (et l’essentiel de la paramétrisation) est inégalée (mis à part LightGBM par Microsoft), ainsi que H2O codé en Java (relativement beaucoup plus lent, même si plus rapide que la plupart des libraries de machine learning). Nous ne sauvegardons pas encore de modèles. Ces deux libraries disposent de modèles exportables qui peuvent être enregistrés et déployés respectivement en C/shell (xgboost) et Java (H2O) pour un usage immédiat.
Nous n’utilisons pas un KNN (voisins les plus proches) ni un SVM (Support Vector Machines) pour leurs problèmes d’entrainement et de déploiement (KNN défère l’entrainement à la prédiction, et SVM a une complexité de calcul telle que ce ne sont pas des modèles déployables de manière efficiente en entreprise).
Les objectifs seront les suivants :
- Minimiser l’erreur de classification
- Ne pas dépasser 25% de features (52) par rapport au nombre d’observations par fold pour éviter le surapprentissage des features (qui ne se repérera pas sur les métriques de validation avec si peu d’échantillons, qui est le problème de fluctuation par erreur)
- Dépasser le seuil du modèle “aléatoire” (53/210 + 53/208 + 52/210, soit 05.02f%)
On prépare d’abord les pré-requis pour le calcul, qui sont :
- Les folds à utiliser pour la cross-validation : on découpera de manière disjointe les salles, afin d’éviter tout leakage à l’intérieur des salles (afin d’empêcher le modèle de machine learning d’apprendre les salles et non pas la généralisation à des échantillon inconnus dans de nouvelles salles)
- La métrique d’évaluation, où ici l’exactitude des prédictions est multi-classe
Comme nous allons découper selon différentes méthodes, nous devrons créer à la fois les fold de train mais aussi de test :
- Entrainement sur une salle, prédiction sur une autre salle (1 contre 1)
- Entrainement sur une salle, prédiction sur deux autres salles (1 contre 2)
- Entrainement sur deux salles, prédiction sur une autre salle (2 contre 1)
La moyenne générale sera agrégée à partir des trois moyennes agrégées (1 contre 1, 1 contre 2, et 2 contre 1). Au total, nous avons 144 modèles (12 modèles, 12 sets d’entrainement/validation) à tester. Vu la taille des données (314 observations, moins d’une centaine de features) et la mémoire vive disponible (plus de 50 GB), on peut se permettre de pré-créer toutes les datasets au lieu de les créer à la volée.
Le temps perdu à la création des datasets ici est due en grande partie due à H2O, dont les objets doivent être traduits en Java à partir de R.
Les fichiers sont enregistrés avec la nomenclature suivante :
- NL pour “No Label” (données non labellisées)
- L pour “Label” (données labellisées)
- .csv pour le format CSV (Comma-Separated Values)
- .data pour le format binaire xgboost (LightSVM compressé)
# Compteur de temps
CurrentTime <- timer() # Chunk Préparation de l'évaluation du modèle
# Où sauvegarder les fichiers ?
file_tag <- "1_data/"
# Initialisation de la variable qui accueillera la précision
accuracy <- data.frame(matrix(nrow = 16, ncol = 13))
colnames(accuracy) <- c("Fold", "xgb_LinearModel", "xgb_DecisionTree", "xgb_RandomForest", "xgb_GradientBoosting", "h2o_LinearModel", "h2o_DecisionTree", "h2o_RandomForest", "h2o_GradientBoosting", "h2o_NN_32x6_ReLU", "h2o_NN_32x6_Soft", "h2o_NN_16x16x6_ReLU", "h2o_NN_16x16x6_Soft")
accuracy[, 1] <- c("Fold_1v2", "Fold_1v3", "Fold_2v1", "Fold_2v3", "Fold_3v1", "Fold_3v2", "Fold_1v23", "Fold_2v13", "Fold_3v12", "Fold_12v3", "Fold_13v2", "Fold_23v1", "Moyenne_1c1", "Moyenne_1c2", "Moyenne_2c1", "Moyenne")
# Initialisation des folds pour la cross-validation
folds_train <- list()
folds_test <- list()
training_data <- list()
testing_data <- list()
training_xgb <- list()
testing_xgb <- list()
training_h2o <- list()
testing_h2o <- list()
combinations_train <- c(list(1, 1, 2, 2, 3, 3), combn(3, 1, simplify = FALSE), combn(3, 2, simplify = FALSE))
combinations_test <- c(list(2, 3, 1, 3, 1, 2), rev(combn(3, 2, simplify = FALSE)), rev(combn(3, 1, simplify = FALSE)))
temp_factors <- as.factor(group_path$path_ID)
# Création des données d'entrainement et de validation
for (i in 1:12) {
# Création des folds d'entrainement et de validation
folds_train[[i]] <- which(group_room[["dataset_ID"]] %in% combinations_train[[i]])
folds_test[[i]] <- which(group_room[["dataset_ID"]] %in% combinations_test[[i]])
# Recherche et suppression du label 3 lorsque la salle 1 est isolée (soit en train on enlève en test, soit en test on enlève en train)
if ((length(combinations_train[[i]]) == 1) & (combinations_train[[i]][1] == 1)) {
folds_test[[i]] <- folds_test[[i]][group_path$path_ID[folds_test[[i]]] != 3]
}
if ((length(combinations_test[[i]]) == 1) & (combinations_test[[i]][1] == 1)) {
folds_train[[i]] <- folds_train[[i]][group_path$path_ID[folds_train[[i]]] != 3]
}
# Création des données d'entrainement et de validation
training_data[[i]] <- mini_lm[folds_train[[i]], ]
testing_data[[i]] <- mini_lm[folds_test[[i]], ]
# Enregistrement des données CSV
fwrite(training_data[[i]], paste0(file_tag, "trainNL_", sprintf("%02d", i), ".csv"))
fwrite(testing_data[[i]], paste0(file_tag, "testNL_", sprintf("%02d", i), ".csv"))
# Transformation des données au format approprié pour xgboost
training_xgb[[i]] <- xgb.DMatrix(data = as.matrix(training_data[[i]]), label = group_path$path_ID[folds_train[[i]]] - 1)
testing_xgb[[i]] <- xgb.DMatrix(data = as.matrix(testing_data[[i]]), label = group_path$path_ID[folds_test[[i]]] - 1)
# Dumping des datasets binaires xgboost
xgb.DMatrix.save(training_xgb[[i]], paste0(file_tag, "trainL_", sprintf("%02d", i), ".data"))
xgb.DMatrix.save(testing_xgb[[i]], paste0(file_tag, "testL_", sprintf("%02d", i), ".data"))
# Transformation des données au format approprié pour H2O
training_h2o[[i]] <- as.h2o(cbind(Label = temp_factors[folds_train[[i]]], training_data[[i]]))
testing_h2o[[i]] <- as.h2o(cbind(Label = temp_factors[folds_test[[i]]], testing_data[[i]]))
# Enregistrement des frames H2O (CSV + Label)
h2o.exportFile(training_h2o[[i]], paste0(file_tag, "trainL_", sprintf("%02d", i), ".csv"), force = TRUE)
h2o.exportFile(testing_h2o[[i]], paste0(file_tag, "testL_", sprintf("%02d", i), ".csv"), force = TRUE)
}
# Temps nécessaire
timing(CurrentTime, "Préparation de l'évaluation du modèle")
Temps d'exécution de la tâche 'Préparation de l'évaluation du modèle' : 20.64 secondes.
Dans le cas où on souhaiterait faire une classification binaire, utiliser un seuil dynamique pour découper les labels est plus adéquat (par rapport à 0.50 comme seuil). On ne réalise pas de classification binaire ici, mais cela peut toujours servir dans le futur.
# Le backend utilisé par xgboost pour déterminer le seuil à utiliser pour partager les labels 0 et 1
acc_eval <- function(pred, dtrain) {
# Récupération du label
y_true <- getinfo(dtrain, "label")
# Création de la data.table triée avec comme clé primaire la probabilité
DT <- data.table(y_true = y_true, y_prob = pred, key = "y_prob")
# Préparation pour le nettoyage des doublons postérieurs
cleaner <- !duplicated(DT[, "y_prob"], fromLast = TRUE)
# Pré-calcul de variables spécifiques
lens <- length(y_true)
nump <- sum(y_true)
# Détermination des vrais négatifs et des vrais positifs
DT[, tn_v := cumsum(y_true == 0)]
DT[, tp_v := nump - cumsum(y_true == 1)]
# Nettoyage des doublons pour éviter le problème d'ordre par chance
DT <- DT[cleaner, ]
# Détermination de l'exactitude des données
DT[, acc := (tn_v + tp_v) / lens]
# Annulation à zéro pour toute observation dont le calcul aboutit à une erreur
DT[, acc := ifelse(!is.finite(acc), 0, acc)]
# Recherche de la meilleure exactitude
best_row <- which.max(DT$acc)
best_acc <- round(100 * DT$acc[best_row[1]], digits = 8)
# Retour de la meilleure exactitude
return(list(metric = "acc", value = best_acc))
}
Entraînement des douze modèles
Le temps de calcul est principalement du aux modèles H2O qui prennent la majorité du temps de calcul (environ 180 secondes contre quelques secondes pour les 12x4 xgboost). Chaque modèle est entrainé sur un set d’entrainement, et testé sur un set de validation.
Pour utiliser les fichiers Java, il faut utiliser le fichier h2o-genmodel.jar adéquat trouvable ici : http://mvnrepository.com/artifact/ai.h2o/h2o-genmodel
Note : les modèles enregistrés ne sont pas forcément les meilleurs, mais contiennent le modèle final entrainé (avec potentiellement de l’overfitting).
# Compteur de temps
CurrentTime <- timer() # Création et évaluation des douze modèles de benchmark
# Où sauvegarder les fichiers ?
file_tag <- "1_models/"
file_h2o <- "1_models"
xgb_dynamic_train <- function(train, test, booster, nrounds, num_parallel_trees) {
# Fixation du seed du générateur de nombres aléatoires pour qu'on puisse reproduire les résultats sur d'autres machines de manière exacte
set.seed(11111)
# Entrainement du modèle
return(xgb.train(data = train,
num_class = 6, # Classification à 6 classes
nthread = 1, # 1 coeur utilisé
nrounds = nrounds, # Nombre d'itérations de boosting
num_parallel_trees = num_parallel_trees, # Nombre d'arbres pour le mode Random Forest
subsample = ifelse(num_parallel_trees > 1, 0.632, 1), # Bootstrap des données pour l'échantillonnage en mode Random Forest
eta = 0.10, # Shrinkage pour le boosting
booster = booster, # Type d'entrainement : linéaire ou non-linéaire
objective = "multi:softprob", # Gradient/Hessian pour l'optimisation par Gradient Descent
eval_metric = "merror", # Inexactitude de la classification
maximize = FALSE, # Minimisation de l'erreur
early_stopping_rounds = 100, # Arrêt après 100 itérations sans amélioration de la métrique
verbose = FALSE, # Sans print des itérations
watchlist = list(test = test), # Estimation sur les données de test
callbacks = list(cb.evaluation.log()))) # Logging des données d'entrainement pour pouvoir récupérer les métriques
}
h2o_nn_train <- function(train, test, model_id, activation, hidden) {
return(temp_model <- h2o.deeplearning(y = 1, # Label = 1ère colonne
training_frame = train,
validation_frame = test,
model_id = model_id, # Nom du modèle
standardize = FALSE, # Pas de standardisation des données, puisque [-1, 1]
activation = activation, # Activation finale du réseau de neurones
hidden = hidden, # Architecture du réseau de neurones
epochs = 100, # Nombre de passes
loss = "CrossEntropy", # Optimisation Softmax
distribution = "multinomial", # Classification multi-class
stopping_rounds = 10, # Arrêt après 10 itérations sans amélioration spécifique
stopping_metric = "misclassification", # Minimisation de l'erreur
stopping_tolerance = 0.00001, # Tolérance maximale de 0.001% de stagnation de l'erreur
reproducible = TRUE, # Tentative de résultats reproductibles
seed = 0)) # Reproduction des résultats
}
# Boucle d'évaluation
for (i in 1:12) {
# Entrainement du modèle de régression logistique (xgboost)
temp_model <- xgb_dynamic_train(train = training_xgb[[i]],
test = testing_xgb[[i]],
booster = "gblinear", # Linéaire
nrounds = 1000000, # Arrêté au meilleur résultat
num_parallel_trees = 1)
xgb.dump(model = temp_model, # Modèle à enregistrer
fname = paste0(file_tag, "xgb_glm_", sprintf("%02d", i), ".json"), # Où enregistrer le modèle ?
with_stats = TRUE, # Enregistrement des statistiques si modèle gbtree
dump_format = "json") # Dump au format json, ré-utilisable
accuracy[i, 2] <- 1 - temp_model$evaluation_log[[2]][temp_model$best_iteration] # Récupération du meilleur résultat
# Entrainement du modèle d'arbre de décision (xgboost)
temp_model <- xgb_dynamic_train(train = training_xgb[[i]],
test = testing_xgb[[i]],
booster = "gbtree", # Non-linéaire
nrounds = 1, # Un seul arbre
num_parallel_trees = 1)
xgb.dump(model = temp_model, # Modèle à enregistrer
fname = paste0(file_tag, "xgb_dt_", sprintf("%02d", i), ".json"), # Où enregistrer le modèle ?
with_stats = TRUE, # Enregistrement des statistiques si modèle gbtree
dump_format = "json") # Dump au format json, ré-utilisable
accuracy[i, 3] <- 1 - temp_model$evaluation_log[[2]][1] # Récupération du meilleur résultat
# Entrainement du modèle de Random Forest (xgboost)
temp_model <- xgb_dynamic_train(train = training_xgb[[i]],
test = testing_xgb[[i]],
booster = "gbtree", # Non-linéaire
nrounds = 1, # Une seule itération
num_parallel_trees = 200) # De 200 arbres
xgb.dump(model = temp_model, # Modèle à enregistrer
fname = paste0(file_tag, "xgb_rf_", sprintf("%02d", i), ".json"), # Où enregistrer le modèle ?
with_stats = TRUE, # Enregistrement des statistiques si modèle gbtree
dump_format = "json") # Dump au format json, ré-utilisable
accuracy[i, 4] <- 1 - temp_model$evaluation_log[[2]] # Récupération du meilleur résultat
# Entrainement du modèle d'arbre de décision boosté avec protection contre l'overfitting (xgboost)
temp_model <- xgb_dynamic_train(train = training_xgb[[i]],
test = testing_xgb[[i]],
booster = "gbtree", # Non-linéaire
nrounds = 1000000, # Arrêté au meilleur résultat
num_parallel_trees = 1)
xgb.dump(model = temp_model, # Modèle à enregistrer
fname = paste0(file_tag, "xgb_gbt_", sprintf("%02d", i), ".json"), # Où enregistrer le modèle ?
with_stats = TRUE, # Enregistrement des statistiques si modèle gbtree
dump_format = "json") # Dump au format json, ré-utilisable
accuracy[i, 5] <- 1 - temp_model$evaluation_log[[2]][temp_model$best_iteration] # Récupération du meilleur résultat
# Entrainement du modèle de régression logistique (h2o)
temp_model <- h2o.glm(y = 1,
training_frame = training_h2o[[i]],
validation_frame = testing_h2o[[i]],
model_id = paste0("h2o_glm_", sprintf("%02d", i)), # Nom du modèle
max_iterations = 100, # 100 itérations d'optimisation
solver = "IRLSM", # Solveur par défaut
standardize = FALSE, # Pas de standardisation puisque [-1, 1]
family = "multinomial", # Classification multi-classe
seed = 0, # Reproduction des résultats
intercept = TRUE)
h2o.download_pojo(temp_model, # Modèle à enregistrer
path = file_h2o, # Où enregistrer le modèle ?
get_jar = FALSE) # Pas de fichier .jar
accuracy[i, 6] <- temp_model@model$validation_metrics@metrics$hit_ratio_table[1, 2]
# Entrainement du modèle d'arbre de décision (h2o)
temp_model <- h2o.randomForest(y = 1,
training_frame = training_h2o[[i]],
validation_frame = testing_h2o[[i]],
model_id = paste0("h2o_dt_", sprintf("%02d", i)), # Nom du modèle
sample_rate = 1, # Toutes les observations seront prises en compte pour le seul arbre de décision
mtries = 36, # Toutes les features seront prises en compte pour le seul arbre de décision
ntrees = 1, # Un seul arbre
seed = 0) # Reproduction des résultats
h2o.download_pojo(temp_model, # Modèle à enregistrer
path = file_h2o, # Où enregistrer le modèle ?
get_jar = FALSE) # Pas de fichier .jar
accuracy[i, 7] <- 1 - min(temp_model@model$scoring_history$validation_classification_error, na.rm = TRUE)
# Entrainement du modèle de Random Forest (h2o)
temp_model <- h2o.randomForest(y = 1,
training_frame = training_h2o[[i]],
validation_frame = testing_h2o[[i]],
model_id = paste0("h2o_rf_", sprintf("%02d", i)), # Nom du modèle
sample_rate = 0.632, # Bootstrapping .632 pour chaque arbre de décision
mtries = -1, # sqrt(36) features seront prises en compte pour chaque arbre de décision
ntrees = 200, # 200 arbres
seed = 0) # Reproduction des résultats
h2o.download_pojo(temp_model, # Modèle à enregistrer
path = file_h2o, # Où enregistrer le modèle ?
get_jar = FALSE) # Pas de fichier .jar
accuracy[i, 8] <- 1 - min(temp_model@model$scoring_history$validation_classification_error, na.rm = TRUE)
# Entrainement du modèle d'arbre de décision boosté avec protection contre l'overfitting (h2o)
temp_model <- h2o.gbm(y = 1,
training_frame = training_h2o[[i]],
validation_frame = testing_h2o[[i]],
model_id = paste0("h2o_gbt_", sprintf("%02d", i)), # Nom du modèle
distribution = "multinomial", # Classification multi-classe
sample_rate = 1, # Pas de processus stochastique
ntrees = 100, # 100 itérations de boosting au maximum
score_each_iteration = TRUE, # Noter la valeur de chaque itération
stopping_rounds = 10, # Arrêt après 10 itérations sans amélioraton de la métrique
stopping_metric = "misclassification", # Surveiller l'inexactitude de la classification pour l'arrêt
stopping_tolerance = 0.00001, # Arrêter lorsque la métrique stagne de 0.001%
seed = 0) # Reproduction des résultats
h2o.download_pojo(temp_model, # Modèle à enregistrer
path = file_h2o, # Où enregistrer le modèle ?
get_jar = FALSE) # Pas de fichier .jar
accuracy[i, 9] <- 1 - min(temp_model@model$scoring_history$validation_classification_error, na.rm = TRUE)
# Entrainement du réseau de neurones à architecture 32x6 + ReLU (h2o)
temp_model <- h2o_nn_train(train = training_h2o[[i]],
test = testing_h2o[[i]],
model_id = paste0("h2o_nn_32x6_ReLU_", sprintf("%02d", i)), # Nom du modèle
activation = "Rectifier", # ReLU
hidden = 32) # Architecture 32x6
h2o.download_pojo(temp_model, # Modèle à enregistrer
path = file_h2o, # Où enregistrer le modèle ?
get_jar = FALSE) # Pas de fichier .jar
accuracy[i, 10] <- 1 - min(temp_model@model$scoring_history$validation_classification_error, na.rm = TRUE)
# Entrainement du réseau de neurones à architecture 32x6 + Tanh (h2o)
temp_model <- h2o_nn_train(train = training_h2o[[i]],
test = testing_h2o[[i]],
model_id = paste0("h2o_nn_32x6_Tanh_", sprintf("%02d", i)), # Nom du modèle
activation = "Tanh", # "Sigmoide"
hidden = 32) # Architecture 32x6
h2o.download_pojo(temp_model, # Modèle à enregistrer
path = file_h2o, # Où enregistrer le modèle ?
get_jar = FALSE) # Pas de fichier .jar
accuracy[i, 11] <- 1 - min(temp_model@model$scoring_history$validation_classification_error, na.rm = TRUE)
# Entrainement du réseau de neurones à architecture 16x16x6 + ReLU (h2o)
temp_model <- h2o_nn_train(train = training_h2o[[i]],
test = testing_h2o[[i]],
model_id = paste0("h2o_nn_16x16x6_ReLU_", sprintf("%02d", i)), # Nom du modèle
activation = "Rectifier", # ReLU
hidden = c(16, 16)) # Architecture 16x16x6
h2o.download_pojo(temp_model, # Modèle à enregistrer
path = file_h2o, # Où enregistrer le modèle ?
get_jar = FALSE) # Pas de fichier .jar
accuracy[i, 12] <- 1 - min(temp_model@model$scoring_history$validation_classification_error, na.rm = TRUE)
# Entrainement du réseau de neurones à architecture 16x16x6 + Tanh (h2o)
temp_model <- h2o_nn_train(train = training_h2o[[i]],
test = testing_h2o[[i]],
model_id = paste0("h2o_nn_16x16x6_Tanh_", sprintf("%02d", i)), # Nom du modèle
activation = "Tanh", # "Sigmoide"
hidden = c(16, 16)) # Architecture 16x16x6
h2o.download_pojo(temp_model, # Modèle à enregistrer
path = file_h2o, # Où enregistrer le modèle ?
get_jar = FALSE) # Pas de fichier .jar
accuracy[i, 13] <- 1 - min(temp_model@model$scoring_history$validation_classification_error, na.rm = TRUE)
}
# Temps nécessaire
timing(CurrentTime, "Création et évaluation des douze modèles de benchmark")
Temps d'exécution de la tâche 'Création et évaluation des douze modèles de benchmark' : 178.52 secondes.
Analyse des résultats pour les douze modèles
Nous remarquons clairement le problème qu’on a soulevé dés le début : la salle 1 possède des features qui ne sont pas adéquates en l’état, les données semble bien inutilisables sans transformation préalable.
De fait, les résultats ne sont pas encore exploitables de manière correcte.
# Moyenne des résultats
for (i in 2:13) {
accuracy[13, i] <- mean(accuracy[1:6, i])
accuracy[14, i] <- mean(accuracy[7:9, i])
accuracy[15, i] <- mean(accuracy[10:12, i])
accuracy[16, i] <- mean(accuracy[13:15, i])
}
# Enregistrement des scores
fwrite(accuracy, "scores/1_models.csv")
# Affichage des résultats dans un tableau interactif
to_print <- data.table(t(accuracy[13:16, -1])) # Préparation des données à mettre sur table
colnames(to_print) <- c("1 contre 1", "1 contre 2", "2 contre 1", "Moyenne") # Remise des noms des colonnes
row.names(to_print) <- colnames(accuracy)[-1] # Remise des noms des lignes
datatable(to_print,
filter = "top", # Filtrage au-dessus de la table
class = "cell-border stripe", # CSS
extensions = c("ColReorder",
"RowReorder"), # Reordonner manuellement à la main
options = list(pageLength = 12, # Page affichant 12 lignes
order = list(list(4, "desc")), # Ordonner par défaut par l'exactitude moyenne
colReorder = TRUE, # Plugin
rowReorder = TRUE)) %>% # Plugin
formatStyle(c("1 contre 1", "1 contre 2", "2 contre 1"),
background = styleColorBar(c(0, 1), 'lightgreen'), # Couleur vert clair pour les métriques par fold
backgroundSize = '100% 90%',
backgroundRepeat = 'no-repeat',
backgroundPosition = 'center') %>%
formatStyle("Moyenne",
background = styleColorBar(c(0, 1), 'pink'), # Couleur rose pour la métrique de moyenne
backgroundSize = '100% 90%',
backgroundRepeat = 'no-repeat',
backgroundPosition = 'center') %>%
formatPercentage(columns = c("1 contre 1", "1 contre 2", "2 contre 1"),
digits = 8) %>%
formatPercentage(columns = "Moyenne",
digits = 8)
# Affichage des résultats dans un tableau statique
formattable(accuracy[, c(1, 2:5)], list(formattable::area(col = xgb_LinearModel:xgb_GradientBoosting) ~ color_bar("orange")))
formattable(accuracy[, c(1, 6:9)], list(formattable::area(col = h2o_LinearModel:h2o_GradientBoosting) ~ color_bar("cyan")))
formattable(accuracy[, c(1, 10:13)], list(formattable::area(col = h2o_NN_32x6_ReLU:h2o_NN_16x16x6_Soft) ~ color_bar("yellow")))
Seconde Analyse Systémique
Vu que nous disposons de features nettoyées, nous pouvons procéder à l’analyse systémique de zéro. Crééons d’abord les différents jeux de données.
# Compteur de temps
CurrentTime <- timer() # Préparation de l'évaluation du modèle nettoyé
# Où sauvegarder les fichiers ?
file_tag <- "2_data/"
# Initialisation de la variable qui accueillera la précision
accuracy <- data.frame(matrix(nrow = 16, ncol = 13))
colnames(accuracy) <- c("Fold", "xgb_LinearModel", "xgb_DecisionTree", "xgb_RandomForest", "xgb_GradientBoosting", "h2o_LinearModel", "h2o_DecisionTree", "h2o_RandomForest", "h2o_GradientBoosting", "h2o_NN_32x6_ReLU", "h2o_NN_32x6_Soft", "h2o_NN_16x16x6_ReLU", "h2o_NN_16x16x6_Soft")
accuracy[, 1] <- c("Fold_1v2", "Fold_1v3", "Fold_2v1", "Fold_2v3", "Fold_3v1", "Fold_3v2", "Fold_1v23", "Fold_2v13", "Fold_3v12", "Fold_12v3", "Fold_13v2", "Fold_23v1", "Moyenne_1c1", "Moyenne_1c2", "Moyenne_2c1", "Moyenne")
# Initialisation des folds pour la cross-validation
folds_train <- list()
folds_test <- list()
training_data <- list()
testing_data <- list()
training_xgb <- list()
testing_xgb <- list()
training_h2o <- list()
testing_h2o <- list()
combinations_train <- c(list(1, 1, 2, 2, 3, 3), combn(3, 1, simplify = FALSE), combn(3, 2, simplify = FALSE))
combinations_test <- c(list(2, 3, 1, 3, 1, 2), rev(combn(3, 2, simplify = FALSE)), rev(combn(3, 1, simplify = FALSE)))
temp_factors <- as.factor(group_path$path_ID)
# Création des données d'entrainement et de validation
for (i in 1:12) {
# Création des folds d'entrainement et de validation
folds_train[[i]] <- which(group_room[["dataset_ID"]] %in% combinations_train[[i]])
folds_test[[i]] <- which(group_room[["dataset_ID"]] %in% combinations_test[[i]])
# Recherche et suppression du label 3 lorsque la salle 1 est isolée (soit en train on enlève en test, soit en test on enlève en train)
if ((length(combinations_train[[i]]) == 1) & (combinations_train[[i]][1] == 1)) {
folds_test[[i]] <- folds_test[[i]][group_path$path_ID[folds_test[[i]]] != 3]
}
if ((length(combinations_test[[i]]) == 1) & (combinations_test[[i]][1] == 1)) {
folds_train[[i]] <- folds_train[[i]][group_path$path_ID[folds_train[[i]]] != 3]
}
# Création des données d'entrainement et de validation
training_data[[i]] <- mini_lm[folds_train[[i]], ]
testing_data[[i]] <- mini_lm[folds_test[[i]], ]
# Enregistrement des données CSV
fwrite(training_data[[i]], paste0(file_tag, "trainNL_", sprintf("%02d", i), ".csv"))
fwrite(testing_data[[i]], paste0(file_tag, "testNL_", sprintf("%02d", i), ".csv"))
# Transformation des données au format approprié pour xgboost
training_xgb[[i]] <- xgb.DMatrix(data = as.matrix(training_data[[i]]), label = group_path$path_ID[folds_train[[i]]] - 1)
testing_xgb[[i]] <- xgb.DMatrix(data = as.matrix(testing_data[[i]]), label = group_path$path_ID[folds_test[[i]]] - 1)
# Dumping des datasets binaires xgboost
xgb.DMatrix.save(training_xgb[[i]], paste0(file_tag, "trainL_", sprintf("%02d", i), ".data"))
xgb.DMatrix.save(testing_xgb[[i]], paste0(file_tag, "testL_", sprintf("%02d", i), ".data"))
# Transformation des données au format approprié pour H2O
training_h2o[[i]] <- as.h2o(cbind(Label = temp_factors[folds_train[[i]]], training_data[[i]]))
testing_h2o[[i]] <- as.h2o(cbind(Label = temp_factors[folds_test[[i]]], testing_data[[i]]))
# Enregistrement des frames H2O (CSV + Label)
h2o.exportFile(training_h2o[[i]], paste0(file_tag, "trainL_", sprintf("%02d", i), ".csv"), force = TRUE)
h2o.exportFile(testing_h2o[[i]], paste0(file_tag, "testL_", sprintf("%02d", i), ".csv"), force = TRUE)
}
# Temps nécessaire
timing(CurrentTime, "Préparation de l'évaluation du modèle nettoyé")
Temps d'exécution de la tâche 'Préparation de l'évaluation du modèle nettoyé' : 17.22 secondes.
Nouvel entraînement des douze modèles
Nous pouvons relancer les calculs pour déterminer la performance des modèles. Un entrainement de tous les modèles est bien sûr nécessaire, ainsi que la validation de leurs performances sur des échantillons “inconnus”.
# Compteur de temps
CurrentTime <- timer() # Chunk Création et évaluation des douze modèles de benchmark nettoyé
# Où sauvegarder les fichiers ?
file_tag <- "2_models/"
file_h2o <- "2_models"
# Boucle d'évaluation
for (i in 1:12) {
# Entrainement du modèle de régression logistique (xgboost)
temp_model <- xgb_dynamic_train(train = training_xgb[[i]],
test = testing_xgb[[i]],
booster = "gblinear", # Linéaire
nrounds = 1000000, # Arrêté au meilleur résultat
num_parallel_trees = 1)
xgb.dump(model = temp_model, # Modèle à enregistrer
fname = paste0(file_tag, "xgb_glm_", sprintf("%02d", i), ".json"), # Où enregistrer le modèle ?
with_stats = TRUE, # Enregistrement des statistiques si modèle gbtree
dump_format = "json") # Dump au format json, ré-utilisable
accuracy[i, 2] <- 1 - temp_model$evaluation_log[[2]][temp_model$best_iteration] # Récupération du meilleur résultat
# Entrainement du modèle d'arbre de décision (xgboost)
temp_model <- xgb_dynamic_train(train = training_xgb[[i]],
test = testing_xgb[[i]],
booster = "gbtree", # Non-linéaire
nrounds = 1, # Un seul arbre
num_parallel_trees = 1)
xgb.dump(model = temp_model, # Modèle à enregistrer
fname = paste0(file_tag, "xgb_dt_", sprintf("%02d", i), ".json"), # Où enregistrer le modèle ?
with_stats = TRUE, # Enregistrement des statistiques si modèle gbtree
dump_format = "json") # Dump au format json, ré-utilisable
accuracy[i, 3] <- 1 - temp_model$evaluation_log[[2]][1] # Récupération du meilleur résultat
# Entrainement du modèle de Random Forest (xgboost)
temp_model <- xgb_dynamic_train(train = training_xgb[[i]],
test = testing_xgb[[i]],
booster = "gbtree", # Non-linéaire
nrounds = 1, # Une seule itération
num_parallel_trees = 200) # De 200 arbres
xgb.dump(model = temp_model, # Modèle à enregistrer
fname = paste0(file_tag, "xgb_rf_", sprintf("%02d", i), ".json"), # Où enregistrer le modèle ?
with_stats = TRUE, # Enregistrement des statistiques si modèle gbtree
dump_format = "json") # Dump au format json, ré-utilisable
accuracy[i, 4] <- 1 - temp_model$evaluation_log[[2]] # Récupération du meilleur résultat
# Entrainement du modèle d'arbre de décision boosté avec protection contre l'overfitting (xgboost)
temp_model <- xgb_dynamic_train(train = training_xgb[[i]],
test = testing_xgb[[i]],
booster = "gbtree", # Non-linéaire
nrounds = 1000000, # Arrêté au meilleur résultat
num_parallel_trees = 1)
xgb.dump(model = temp_model, # Modèle à enregistrer
fname = paste0(file_tag, "xgb_gbt_", sprintf("%02d", i), ".json"), # Où enregistrer le modèle ?
with_stats = TRUE, # Enregistrement des statistiques si modèle gbtree
dump_format = "json") # Dump au format json, ré-utilisable
accuracy[i, 5] <- 1 - temp_model$evaluation_log[[2]][temp_model$best_iteration] # Récupération du meilleur résultat
# Entrainement du modèle de régression logistique (h2o)
temp_model <- h2o.glm(y = 1,
training_frame = training_h2o[[i]],
validation_frame = testing_h2o[[i]],
model_id = paste0("h2o_glm_", sprintf("%02d", i)), # Nom du modèle
max_iterations = 100, # 100 itérations d'optimisation
solver = "IRLSM", # Solveur par défaut
standardize = FALSE, # Pas de standardisation puisque [-1, 1]
family = "multinomial", # Classification multi-classe
seed = 0, # Reproduction des résultats
intercept = TRUE)
h2o.download_pojo(temp_model, # Modèle à enregistrer
path = file_h2o, # Où enregistrer le modèle ?
get_jar = FALSE) # Pas de fichier .jar
accuracy[i, 6] <- temp_model@model$validation_metrics@metrics$hit_ratio_table[1, 2]
# Entrainement du modèle d'arbre de décision (h2o)
temp_model <- h2o.randomForest(y = 1,
training_frame = training_h2o[[i]],
validation_frame = testing_h2o[[i]],
model_id = paste0("h2o_dt_", sprintf("%02d", i)), # Nom du modèle
sample_rate = 1, # Toutes les observations seront prises en compte pour le seul arbre de décision
mtries = 36, # Toutes les features seront prises en compte pour le seul arbre de décision
ntrees = 1, # Un seul arbre
seed = 0) # Reproduction des résultats
h2o.download_pojo(temp_model, # Modèle à enregistrer
path = file_h2o, # Où enregistrer le modèle ?
get_jar = FALSE) # Pas de fichier .jar
accuracy[i, 7] <- 1 - min(temp_model@model$scoring_history$validation_classification_error, na.rm = TRUE)
# Entrainement du modèle de Random Forest (h2o)
temp_model <- h2o.randomForest(y = 1,
training_frame = training_h2o[[i]],
validation_frame = testing_h2o[[i]],
model_id = paste0("h2o_rf_", sprintf("%02d", i)), # Nom du modèle
sample_rate = 0.632, # Bootstrapping .632 pour chaque arbre de décision
mtries = -1, # sqrt(36) features seront prises en compte pour chaque arbre de décision
ntrees = 200, # 200 arbres
seed = 0) # Reproduction des résultats
h2o.download_pojo(temp_model, # Modèle à enregistrer
path = file_h2o, # Où enregistrer le modèle ?
get_jar = FALSE) # Pas de fichier .jar
accuracy[i, 8] <- 1 - min(temp_model@model$scoring_history$validation_classification_error, na.rm = TRUE)
# Entrainement du modèle d'arbre de décision boosté avec protection contre l'overfitting (h2o)
temp_model <- h2o.gbm(y = 1,
training_frame = training_h2o[[i]],
validation_frame = testing_h2o[[i]],
model_id = paste0("h2o_gbt_", sprintf("%02d", i)), # Nom du modèle
distribution = "multinomial", # Classification multi-classe
sample_rate = 1, # Pas de processus stochastique
ntrees = 100, # 100 itérations de boosting au maximum
score_each_iteration = TRUE, # Noter la valeur de chaque itération
stopping_rounds = 10, # Arrêt après 10 itérations sans amélioraton de la métrique
stopping_metric = "misclassification", # Surveiller l'inexactitude de la classification pour l'arrêt
stopping_tolerance = 0.00001, # Arrêter lorsque la métrique stagne de 0.001%
seed = 0) # Reproduction des résultats
h2o.download_pojo(temp_model, # Modèle à enregistrer
path = file_h2o, # Où enregistrer le modèle ?
get_jar = FALSE) # Pas de fichier .jar
accuracy[i, 9] <- 1 - min(temp_model@model$scoring_history$validation_classification_error, na.rm = TRUE)
# Entrainement du réseau de neurones à architecture 32x6 + ReLU (h2o)
temp_model <- h2o_nn_train(train = training_h2o[[i]],
test = testing_h2o[[i]],
model_id = paste0("h2o_nn_32x6_ReLU_", sprintf("%02d", i)), # Nom du modèle
activation = "Rectifier", # ReLU
hidden = 32) # Architecture 32x6
h2o.download_pojo(temp_model, # Modèle à enregistrer
path = file_h2o, # Où enregistrer le modèle ?
get_jar = FALSE) # Pas de fichier .jar
accuracy[i, 10] <- 1 - min(temp_model@model$scoring_history$validation_classification_error, na.rm = TRUE)
# Entrainement du réseau de neurones à architecture 32x6 + Tanh (h2o)
temp_model <- h2o_nn_train(train = training_h2o[[i]],
test = testing_h2o[[i]],
model_id = paste0("h2o_nn_32x6_Tanh_", sprintf("%02d", i)), # Nom du modèle
activation = "Tanh", # "Sigmoide"
hidden = 32) # Architecture 32x6
h2o.download_pojo(temp_model, # Modèle à enregistrer
path = file_h2o, # Où enregistrer le modèle ?
get_jar = FALSE) # Pas de fichier .jar
accuracy[i, 11] <- 1 - min(temp_model@model$scoring_history$validation_classification_error, na.rm = TRUE)
# Entrainement du réseau de neurones à architecture 16x16x6 + ReLU (h2o)
temp_model <- h2o_nn_train(train = training_h2o[[i]],
test = testing_h2o[[i]],
model_id = paste0("h2o_nn_16x16x6_ReLU_", sprintf("%02d", i)), # Nom du modèle
activation = "Rectifier", # ReLU
hidden = c(16, 16)) # Architecture 16x16x6
h2o.download_pojo(temp_model, # Modèle à enregistrer
path = file_h2o, # Où enregistrer le modèle ?
get_jar = FALSE) # Pas de fichier .jar
accuracy[i, 12] <- 1 - min(temp_model@model$scoring_history$validation_classification_error, na.rm = TRUE)
# Entrainement du réseau de neurones à architecture 16x16x6 + Tanh (h2o)
temp_model <- h2o_nn_train(train = training_h2o[[i]],
test = testing_h2o[[i]],
model_id = paste0("h2o_nn_16x16x6_Tanh_", sprintf("%02d", i)), # Nom du modèle
activation = "Tanh", # "Sigmoide"
hidden = c(16, 16)) # Architecture 16x16x6
h2o.download_pojo(temp_model, # Modèle à enregistrer
path = file_h2o, # Où enregistrer le modèle ?
get_jar = FALSE) # Pas de fichier .jar
accuracy[i, 13] <- 1 - min(temp_model@model$scoring_history$validation_classification_error, na.rm = TRUE)
}
# Temps nécessaire
timing(CurrentTime, "Création et évaluation des douze modèles de benchmark nettoyé")
Temps d'exécution de la tâche 'Création et évaluation des douze modèles de benchmark nettoyé' : 182.64 secondes.
Nouvelle analyse des résultats
Nous voyons clairement que les modèles linéaires se démarquent de tous les autres modèles non-linéaires. Il y a eu une raison toute simple à a cela : les modèles non-linéaires utilisés ne peuvent s’accomoder face à de nouvelles valeurs de manière echelonnée, ce qui est tout le contraire des modèles linéaires !
# Moyenne des résultats
for (i in 2:13) {
accuracy[13, i] <- mean(accuracy[1:6, i])
accuracy[14, i] <- mean(accuracy[7:9, i])
accuracy[15, i] <- mean(accuracy[10:12, i])
accuracy[16, i] <- mean(accuracy[13:15, i])
}
# Enregistrement des scores
fwrite(accuracy, "scores/2_models.csv")
# Affichage des résultats dans un tableau interactif
to_print <- data.table(t(accuracy[13:16, -1])) # Préparation des données à mettre sur table
colnames(to_print) <- c("1 contre 1", "1 contre 2", "2 contre 1", "Moyenne") # Remise des noms des colonnes
row.names(to_print) <- colnames(accuracy)[-1] # Remise des noms des lignes
datatable(to_print,
filter = "top", # Filtrage au-dessus de la table
class = "cell-border stripe", # CSS
extensions = c("ColReorder",
"RowReorder"), # Reordonner manuellement à la main
options = list(pageLength = 12, # Page affichant 12 lignes
order = list(list(4, "desc")), # Ordonner par défaut par l'exactitude moyenne
colReorder = TRUE, # Plugin
rowReorder = TRUE)) %>% # Plugin
formatStyle(c("1 contre 1", "1 contre 2", "2 contre 1"),
background = styleColorBar(c(0, 1), 'lightgreen'), # Couleur vert clair pour les métriques par fold
backgroundSize = '100% 90%',
backgroundRepeat = 'no-repeat',
backgroundPosition = 'center') %>%
formatStyle("Moyenne",
background = styleColorBar(c(0, 1), 'pink'), # Couleur rose pour la métrique de moyenne
backgroundSize = '100% 90%',
backgroundRepeat = 'no-repeat',
backgroundPosition = 'center') %>%
formatPercentage(columns = c("1 contre 1", "1 contre 2", "2 contre 1"),
digits = 8) %>%
formatPercentage(columns = "Moyenne",
digits = 8)
# Affichage des résultats dans un tableau statique
formattable(accuracy[, c(1, 2:5)], list(formattable::area(col = xgb_LinearModel:xgb_GradientBoosting) ~ color_bar("orange")))
formattable(accuracy[, c(1, 6:9)], list(formattable::area(col = h2o_LinearModel:h2o_GradientBoosting) ~ color_bar("cyan")))
formattable(accuracy[, c(1, 10:13)], list(formattable::area(col = h2o_NN_32x6_ReLU:h2o_NN_16x16x6_Soft) ~ color_bar("yellow")))
Démonstration du problème linéaire
Il est travial de démontrer qu’une partie du problème ne peut être résolue de manière non-linéaire en l’état sans une analyse approfondie. Les résidus, par exemple, ont une valeur moyenne bien plus grande dans la salle 3 (plus difficile à prévoir) que les deux autres salles.
Par exemple, les résidus montrent que la salle 1 est à dissocier des deux autres salles. Cela est vérifiable en estimant la différence des features entre la salle 1 et les deux autres salles. Par sécurité, on utilisera le test U de Mann-Whitney à deux bornes, avec calcul exact (sans correction de continuité). Si la p-value est supérieure à 0.05 (si l’on suppose notre seuil de décision à 5% d’erreur), le test ne rejette pas l’hypothèse nulle (différence des médianes égale à zéro). Dans le cas contraire, la différence des médianes est statistiquement supérieure à 0 (les deux échantillons, l’une dans la salle 1, l’autre dans les deux autres salles, sont statistiquement différents). On affichera l’intervalle de confiance à 95% des médianes.
# Compteur de temps
CurrentTime <- timer() # Préparation de l'évaluation du modèle nettoyé
# Pré-initialisation des variables
temp_utest <- data.frame(Feature = c(paste0(rep(c(paste0("Coef", 1:4), paste0("Rési", 1:4)), 4), paste0("_", inverse.rle(list(lengths = rep(8, 4), values = 1:4)))), paste0("PosInitiale_", 1:4)),
p_value = numeric(36),
median_est = numeric(36),
median_inf95 = numeric(36),
median_sup95 = numeric(36))
# Mann-Whitney exact two-tailed U-test
for (i in 1:36) {
temp_whitney <- wilcox.test(mini_lm[[i]][group_path$path_ID == 1], mini_lm[[i]][group_path$path_ID != 1], alternative = "two.sided", paired = FALSE, exact = TRUE, conf.int = TRUE, conf.level = 0.95)
temp_utest[i, 2:5] <- c(temp_whitney$p.value, temp_whitney$estimate, temp_whitney$conf.int)
}
# Dump des données des tests U à deux bornes de Mann-Whitney
fwrite(accuracy, "stats/u_test.csv")
# Tableau interactif du U test de Mann-Whitney
datatable(temp_utest,
filter = "top", # Filtrage au-dessus de la table
class = "cell-border stripe", # CSS
extensions = c("ColReorder",
"RowReorder"), # Reordonner manuellement à la main
options = list(order = list(list(2, "desc")), # Ordonner par défaut par les facteurs ayant la p.value la plus grande
colReorder = TRUE, # Plugin
rowReorder = TRUE)) %>% # Plugin
formatStyle("p_value",
background = styleColorBar(c(0, 1), 'lightblue'), # Couleur bleue pour le coefficient
backgroundSize = '100% 90%',
backgroundRepeat = 'no-repeat',
backgroundPosition = 'center') %>%
formatStyle(c("median_est", "median_inf95", "median_sup95"),
background = styleColorBar(range(temp_utest[, 3:5]), 'pink'), # Couleur rose pour la différence de médiane estimée
backgroundSize = '100% 90%',
backgroundRepeat = 'no-repeat',
backgroundPosition = 'center') %>%
formatRound(columns = c("p_value", "median_est", "median_inf95", "median_sup95"),
digits = 6)
# Création de la table pour la visualisation
temp_lm <- copy(mini_lm)
colnames(temp_lm) <- c(paste0(rep(c(paste0("Coef", 1:4), paste0("Rési", 1:4)), 4), paste0("_", inverse.rle(list(lengths = rep(8, 4), values = 1:4)))), paste0("PosInitiale_", 1:4))
timing(CurrentTime, "Calculs statistiques")
Temps d'exécution de la tâche 'Calculs statistiques' : 777.92 secondes.
# Tableplot des coefficients, trajectoires, et salles
plot(tableplot(dat = cbind(Trajectoire = as.factor(group_path$path_ID), Salle = as.factor(group_room$dataset_ID), temp_lm[, c(1:4, 9:12, 17:20, 25:28)]), sortCol = 2, nBins = 20, scales = "lin", plot = FALSE), title = "Trajectoire vs Coefficients")
Error in file.info(x) : invalid filename argument

# Tableplot des résidus, trajectoires, et salles
plot(tableplot(dat = cbind(Trajectoire = as.factor(group_path$path_ID), Salle = as.factor(group_room$dataset_ID), temp_lm[, c(5:8, 13:16, 21:24, 29:32)]), sortCol = 2, nBins = 20, scales = "lin", plot = FALSE), title = "Trajectoire vs Résidus")
Error in file.info(x) : invalid filename argument

# Tableplot des positions initiales, trajectoires, et salles
plot(tableplot(dat = cbind(Trajectoire = as.factor(group_path$path_ID), Salle = as.factor(group_room$dataset_ID), temp_lm[, c(33:36)]), sortCol = 2, nBins = 20, scales = "lin", plot = FALSE), title = "Trajectoire vs Position Initiale")
Error in file.info(x) : invalid filename argument

Analyse du modèle de régression logistique
Pour analyser le modèle de régression logistique, nous allons utiliser le modèle linéaire xgboost qui est un modèle très simple (équivalent d’une “régression linéaire avec une application sigmoïdale (Softmax)”). Il reste clair que la salle 1 donne toujours des problèmes, mais les résultats sont déjà bien meilleurs avec une exactitude d’environ 57%! (qui est nettement supérieure au modèle aléatoire, devant atteindre uniquement 25.00% de précision).
Pour éviter toute confusion, nous utiliserons la sémantique suivante : Fold = Salle. De plus, nous utiliserons les valeurs absolues afin de ne pas impacter les nombres vers zéros (s’ils paraissent à la fois négatifs et positifs, pour différents folds). Les signes sont donnés séparément.
La matrice de confusion nous montre claireemnt que les chemins 1 et 2 sont problématiques : ils se mélangent. De même pour la salle 3, dont l’exactitude de la prédiction n’est que de 12%.
# Compteur de temps
CurrentTime <- timer() # Chunk Préparation de l'analyse du modèle de régression logistique
# Pré-initialisation des variables
predictedValues <- matrix(nrow = 314, ncol = 6)
evolution <- list()
temp_dt <- list()
temp_means <- data.frame(Feature = c(paste0(rep(c(paste0("Coef", 1:4), paste0("Rési", 1:4)), 4), paste0("_", inverse.rle(list(lengths = rep(8, 4), values = 1:4)))), paste0("PosInitiale_", 1:4)),
Fold_1 = numeric(36),
Fold_2 = numeric(36),
Fold_3 = numeric(36),
Fold_Mean = numeric(36),
Feature_Mean = numeric(36),
Feature_SD = numeric(36))
# Boucle d'entrainement 2 contre 1
for (i in 10:12) {
# Entrainement d'un modèle linéaire
temp_model <- xgb.train(data = training_xgb[[i]],
num_class = 6, # Classification à 6 classes
nthread = 1, # 1 coeur utilisé
nrounds = 1000000, # Nombre d'itérations de boosting
eta = 0.10, # Shrinkage pour le boosting
booster = "gblinear", # Type d'entrainement : linéaire ou non-linéaire
objective = "multi:softprob", # Gradient/Hessian pour l'optimisation par Gradient Descent
eval_metric = "merror", # Inexactitude de la classification
maximize = FALSE, # Minimisation de l'erreur
early_stopping_rounds = 100, # Arrêt après 100 itérations sans amélioration de la métrique
verbose = FALSE, # Sans print des itérations
watchlist = list(test = testing_xgb[[i]]), # Estimation sur les données de test
callbacks = list(cb.evaluation.log())) # Logging des données d'entrainement pour pouvoir récupérer les métriques
# Enregistrement du log
evolution[[i - 9]] <- cbind(temp_model$evaluation_log, Fold = rep(13 - i, temp_model$niter))
# Entrainement du meilleur modèle (obtention des meilleurs coefficients)
temp_model <- xgb.train(data = training_xgb[[i]],
num_class = 6, # Classification à 6 classes
nthread = 1, # 1 coeur utilisé
nrounds = temp_model$best_iteration, # Nombre d'itérations de boosting
eta = 0.10, # Shrinkage pour le boosting
booster = "gblinear", # Type d'entrainement : linéaire ou non-linéaire
objective = "multi:softprob", # Gradient/Hessian pour l'optimisation par Gradient Descent
eval_metric = "merror", # Inexactitude de la classification
maximize = FALSE, # Minimisation de l'erreur
early_stopping_rounds = 99999, # Sans arrêt
verbose = FALSE, # Sans print des itérations
watchlist = list(test = testing_xgb[[i]]), # Estimation sur les données de test
callbacks = list(cb.evaluation.log())) # Logging des données d'entrainement pour pouvoir récupérer les métriques
# Prédiction du modèle linéaire
predictedValues[folds_test[[i]], ] <- t(matrix(predict(temp_model, testing_xgb[[i]], ntreelimit = 0), nrow = 6))
# Calcul et formattage de l'importance des variables
temp_importance <- data.table(Feature = temp_means[["Feature"]],
matrix(xgb.importance(model = temp_model)$Weight, ncol = 6))
colnames(temp_importance) <- c("Feature", paste0("Label_", 1:6))
temp_importance[["Sign"]] <- paste0(ifelse(temp_importance[[2]] >= 0, "+", "-"), ifelse(temp_importance[[3]] >= 0, "+", "-"), ifelse(temp_importance[[4]] >= 0, "+", "-"), ifelse(temp_importance[[5]] >= 0, "+", "-"), ifelse(temp_importance[[6]] >= 0, "+", "-"), ifelse(temp_importance[[7]] >= 0, "+", "-"))
temp_importance[, 2:7] <- abs(temp_importance[, 2:7, with = FALSE])
temp_means[[14 - i]] <- rowMeans(temp_importance[, 2:7, with = FALSE])
temp_importance[[paste0("Fold_", 13 - i, "_Mean")]] <- temp_means[[14 - i]]
# Enregistrement sous forme de tableau interactif
temp_dt[[i - 9]] <- datatable(temp_importance,
filter = "top", # Filtrage au-dessus de la table
class = "cell-border stripe", # CSS
extensions = c("ColReorder",
"RowReorder"), # Reordonner manuellement à la main
options = list(order = list(list(9, "desc")), # Ordonner par défaut par les facteurs ayant le poids le plus gros
colReorder = TRUE, # Plugin
rowReorder = TRUE)) %>% # Plugin
formatStyle(paste0("Label_", 1:6),
background = styleColorBar(range(temp_importance[, 2:7, with = FALSE]), 'lightblue'), # Couleur bleue pour le coefficient
backgroundSize = '100% 90%',
backgroundRepeat = 'no-repeat',
backgroundPosition = 'center') %>%
formatStyle(paste0("Fold_", 13 - i, "_Mean"),
background = styleColorBar(range(temp_importance[[9]]), 'pink'), # Couleur rose pour la métrique de moyenne
backgroundSize = '100% 90%',
backgroundRepeat = 'no-repeat',
backgroundPosition = 'center') %>%
formatRound(columns = c(paste0("Label_", 1:6), paste0("Fold_", 13 - i, "_Mean")),
digits = 6)
}
# Calcul du poids moyen affecté à chaque feature
temp_means[[5]] <- rowMeans(temp_means[, 2:4]) # Poids moyen
temp_means[[6]] <- apply(mini_lm, 2, function(x) {mean(x)}) # Moyenne de la feature dans les données
temp_means[[7]] <- apply(mini_lm, 2, function(x) {sd(x)}) # Ecart-type de la feature dans les données
# Prépration du tableau interactif sur les poids moyens agrégés
temp_dt[[4]] <- datatable(temp_means,
filter = "top", # Filtrage au-dessus de la table
class = "cell-border stripe", # CSS
extensions = c("ColReorder",
"RowReorder"), # Reordonner manuellement à la main
options = list(order = list(list(5, "desc")), # Ordonner par défaut par les facteurs ayant le poids le plus gros en moyenne
colReorder = TRUE, # Plugin
rowReorder = TRUE)) %>% # Plugin
formatStyle(paste0("Fold_", 1:3),
background = styleColorBar(range(temp_means[, 2:4]), 'lightblue'), # Couleur bleue pour le coefficient
backgroundSize = '100% 90%',
backgroundRepeat = 'no-repeat',
backgroundPosition = 'center') %>%
formatStyle("Fold_Mean",
background = styleColorBar(range(temp_means[[5]]), 'pink'), # Couleur rose pour la métrique de moyenne
backgroundSize = '100% 90%',
backgroundRepeat = 'no-repeat',
backgroundPosition = 'center') %>%
formatStyle("Feature_Mean",
background = styleColorBar(range(temp_means[[6]]), 'lightgreen'), # Couleur verte pour la moyenne des features
backgroundSize = '100% 90%',
backgroundRepeat = 'no-repeat',
backgroundPosition = 'center') %>%
formatStyle("Feature_SD",
background = styleColorBar(range(temp_means[[7]]), 'orange'), # Couleur verte pour l'écart-type des features
backgroundSize = '100% 90%',
backgroundRepeat = 'no-repeat',
backgroundPosition = 'center') %>%
formatRound(columns = c(paste0("Fold_", 1:3), "Fold_Mean", "Feature_Mean", "Feature_SD"),
digits = 6)
# Dépivotage du log
evolution <- rbindlist(evolution)
colnames(evolution) <- c("Iteration", "Exactitude", "Fold")
evolution$Exactitude <- 1 - evolution$Exactitude
evolution$Fold <- as.factor(evolution$Fold)
# Prédiction à partir des probabilités
predictedLabel <- data.frame(Label = group_path$path_ID, Prediction = apply(predictedValues, 1, function(x) {which.max(x)}))
timing(CurrentTime, "Préparation de l'analyse du modèle de régression logistique")
Temps d'exécution de la tâche 'Préparation de l'analyse du modèle de régression logistique' : 5.54 secondes.
# Affichage de l'évolution de la performance du modèle selon le nombre d'itération, sous forme de plot interactif
ggplotly(ggplot(data = evolution, aes_string(x = "Iteration", y = "Exactitude", group = "Fold", color = "Fold")) + geom_line() + geom_point() + scale_color_brewer(palette = "Set2") + theme_bw() + labs(title = "Evolution de l'exactitude par rapport au nombre d'itérations d'entrainement"), width = 960, height = 720)
# Affichage de la matrice de confusion sous forme de plot interactif
confusion_mat <- expand.grid(Label = 1:6, Prediction = 1:6)
confusion_mat <- merge(confusion_mat, data.table(predictedLabel)[, list(Freq = sum(.N)), by = list(Label, Prediction)], by = c("Label", "Prediction"), all.x = TRUE)
confusion_mat[["Freq"]][is.na(confusion_mat[["Freq"]])] <- 0
ggplotly(ggplot() + geom_rect(data = data.frame(cent = 1:6), size = 2, fill = NA, colour = "black", aes(xmin = cent - 0.5, xmax = cent + 0.5, ymin = cent - 0.5, ymax = cent + 0.5)) + geom_tile(data = confusion_mat, aes_string(x = "Label", y = "Prediction", fill = "Freq")) + geom_text(data = confusion_mat, aes_string(x = "Label", y = "Prediction", label = "Freq")) + scale_x_discrete(name = "Trajectoire Réelle") + scale_y_discrete(name = "Trajectoire Prédite") + scale_fill_gradientn(colours = rev(brewer.pal_extended(3, "PiYG"))) + labs(title = "Matrice de Confusion de la Trajectoire", fill = "Fréquence"), width = 960, height = 720)
# Affichage des tables à la fin car le formattage possède un bug inhérent lorsqu'on a plusieurs datatables (DT) dans le même chunk
# htmltools::tagList(temp_dt[[3]], temp_dt[[2]], temp_dt[[1]], temp_dt[[4]])
Troisième Analyse Systémique
Notre dernière analyse systémique porte sur l’utilisation des ancres qui sont censées être correctes : il faut inverser les ancres 1 et 3, et 2 et 4 de la salle 1.
Correction finale des ancres
Pour corriger les ancres, il suffit de réaliser cette opération sur la salle 1 :
- Ancre 1 => Ancre 3
- Ancre 2 => Ancre 4
- Ancre 3 => Ancre 1
- Ancre 4 => Ancre 2
# Compteur de temps
CurrentTime <- timer() # Chunk Correction des ancres
# Recopie des features dans le sens correct (ancre 1<=>3, ancre 2<=>4)
for (i in which(group_room$dataset_ID == 1)) {
data_pre[[i]][[1]] <- data_pre_old[[i]][[3]]
data_pre[[i]][[3]] <- data_pre_old[[i]][[1]]
data_pre[[i]][[2]] <- data_pre_old[[i]][[4]]
data_pre[[i]][[4]] <- data_pre_old[[i]][[2]]
}
# Enregistrement des frames corrigées et des anciennes frames, pour avoir un set solide, en respectant la nomenclature initiale MovementAAL_RSS_xxx.csv.
for (i in 1:314) {
fwrite(data_pre[[i]], paste0("dataset_corrected/MovementAAL_RSS_", i, ".csv"))
}
# Temps nécessaire
timing(CurrentTime, "Correction des ancres")
Temps d'exécution de la tâche 'Correction des ancres' : 0.68 secondes.
Création des features corrigées
Une fois que les ancres correspondant aux bonnes ancres dans la salle 1, on peut créer les features corrigées.
# Compteur de temps
CurrentTime <- timer() # Chunk Création des features finales
# Pré-initialisation de la frame
mini_lm <- data.frame(matrix(nrow = 314, ncol = 36))
# Boucle par série temporelle
for (i in 1:314) {
# Boucle par ancre
for (j in 1:4) {
# Entrainement d'un modèle linéaire utilisant les autres ancres, avec l'interceptrice
temp_model <- fastLmPure(X = cbind(as.matrix(data_pre[[i]][, (1:4)[-j], with = FALSE]), rep(1, nrow(data_pre[[i]]))), y = data_pre[[i]][[j]])
# Enregistrement des coefficients et des résidus
mini_lm[i, (j * 8 - 7):(j * 8)] <- c(temp_model$coefficients, temp_model$stderr)
}
# Ajout du dernier élément de la série temporelle (4 ancres)
mini_lm[i, 33:36] <- data_pre[[i]][nrow(data_pre[[i]]), ]
}
# Enregistrement des données au format CSV
fwrite(cbind(mini_lm, Group = group_room[["dataset_ID"]], Label = group_path[["path_ID"]]), "features/features3.csv")
# Temps nécessaire
timing(CurrentTime, "Création des features finales")
Temps d'exécution de la tâche 'Création des features finales' : 6.23 secondes.
Enregistrement des features
Une fois corrigées, on enregistre les features comme on a fait au début.
# Compteur de temps
CurrentTime <- timer() # Préparation de l'évaluation du modèle final
# Où sauvegarder les fichiers ?
file_tag <- "3_data/"
# Initialisation de la variable qui accueillera la précision
accuracy <- data.frame(matrix(nrow = 16, ncol = 13))
colnames(accuracy) <- c("Fold", "xgb_LinearModel", "xgb_DecisionTree", "xgb_RandomForest", "xgb_GradientBoosting", "h2o_LinearModel", "h2o_DecisionTree", "h2o_RandomForest", "h2o_GradientBoosting", "h2o_NN_32x6_ReLU", "h2o_NN_32x6_Soft", "h2o_NN_16x16x6_ReLU", "h2o_NN_16x16x6_Soft")
accuracy[, 1] <- c("Fold_1v2", "Fold_1v3", "Fold_2v1", "Fold_2v3", "Fold_3v1", "Fold_3v2", "Fold_1v23", "Fold_2v13", "Fold_3v12", "Fold_12v3", "Fold_13v2", "Fold_23v1", "Moyenne_1c1", "Moyenne_1c2", "Moyenne_2c1", "Moyenne")
# Initialisation des folds pour la cross-validation
folds_train <- list()
folds_test <- list()
training_data <- list()
testing_data <- list()
training_xgb <- list()
testing_xgb <- list()
training_h2o <- list()
testing_h2o <- list()
combinations_train <- c(list(1, 1, 2, 2, 3, 3), combn(3, 1, simplify = FALSE), combn(3, 2, simplify = FALSE))
combinations_test <- c(list(2, 3, 1, 3, 1, 2), rev(combn(3, 2, simplify = FALSE)), rev(combn(3, 1, simplify = FALSE)))
temp_factors <- as.factor(group_path$path_ID)
# Création des données d'entrainement et de validation
for (i in 1:12) {
# Création des folds d'entrainement et de validation
folds_train[[i]] <- which(group_room[["dataset_ID"]] %in% combinations_train[[i]])
folds_test[[i]] <- which(group_room[["dataset_ID"]] %in% combinations_test[[i]])
# Recherche et suppression du label 3 lorsque la salle 1 est isolée (soit en train on enlève en test, soit en test on enlève en train)
if ((length(combinations_train[[i]]) == 1) & (combinations_train[[i]][1] == 1)) {
folds_test[[i]] <- folds_test[[i]][group_path$path_ID[folds_test[[i]]] != 3]
}
if ((length(combinations_test[[i]]) == 1) & (combinations_test[[i]][1] == 1)) {
folds_train[[i]] <- folds_train[[i]][group_path$path_ID[folds_train[[i]]] != 3]
}
# Création des données d'entrainement et de validation
training_data[[i]] <- mini_lm[folds_train[[i]], ]
testing_data[[i]] <- mini_lm[folds_test[[i]], ]
# Enregistrement des données CSV
fwrite(training_data[[i]], paste0(file_tag, "trainNL_", sprintf("%02d", i), ".csv"))
fwrite(testing_data[[i]], paste0(file_tag, "testNL_", sprintf("%02d", i), ".csv"))
# Transformation des données au format approprié pour xgboost
training_xgb[[i]] <- xgb.DMatrix(data = as.matrix(training_data[[i]]), label = group_path$path_ID[folds_train[[i]]] - 1)
testing_xgb[[i]] <- xgb.DMatrix(data = as.matrix(testing_data[[i]]), label = group_path$path_ID[folds_test[[i]]] - 1)
# Dumping des datasets binaires xgboost
xgb.DMatrix.save(training_xgb[[i]], paste0(file_tag, "trainL_", sprintf("%02d", i), ".data"))
xgb.DMatrix.save(testing_xgb[[i]], paste0(file_tag, "testL_", sprintf("%02d", i), ".data"))
# Transformation des données au format approprié pour H2O
training_h2o[[i]] <- as.h2o(cbind(Label = temp_factors[folds_train[[i]]], training_data[[i]]))
testing_h2o[[i]] <- as.h2o(cbind(Label = temp_factors[folds_test[[i]]], testing_data[[i]]))
# Enregistrement des frames H2O (CSV + Label)
h2o.exportFile(training_h2o[[i]], paste0(file_tag, "trainL_", sprintf("%02d", i), ".csv"), force = TRUE)
h2o.exportFile(testing_h2o[[i]], paste0(file_tag, "testL_", sprintf("%02d", i), ".csv"), force = TRUE)
}
# Temps nécessaire
timing(CurrentTime, "Préparation de l'évaluation du modèle final")
Temps d'exécution de la tâche 'Préparation de l'évaluation du modèle final' : 18.95 secondes.
Entrainement des douze modèles
On peut maintenant tester les douze modèles de manière fiable.
# Compteur de temps
CurrentTime <- timer() # Chunk Création et évaluation des douze modèles de benchmark final
# Où sauvegarder les fichiers ?
file_tag <- "3_models/"
file_h2o <- "3_models"
# Boucle d'évaluation
for (i in 1:12) {
# Entrainement du modèle de régression logistique (xgboost)
temp_model <- xgb_dynamic_train(train = training_xgb[[i]],
test = testing_xgb[[i]],
booster = "gblinear", # Linéaire
nrounds = 1000000, # Arrêté au meilleur résultat
num_parallel_trees = 1)
xgb.dump(model = temp_model, # Modèle à enregistrer
fname = paste0(file_tag, "xgb_glm_", sprintf("%02d", i), ".json"), # Où enregistrer le modèle ?
with_stats = TRUE, # Enregistrement des statistiques si modèle gbtree
dump_format = "json") # Dump au format json, ré-utilisable
accuracy[i, 2] <- 1 - temp_model$evaluation_log[[2]][temp_model$best_iteration] # Récupération du meilleur résultat
# Entrainement du modèle d'arbre de décision (xgboost)
temp_model <- xgb_dynamic_train(train = training_xgb[[i]],
test = testing_xgb[[i]],
booster = "gbtree", # Non-linéaire
nrounds = 1, # Un seul arbre
num_parallel_trees = 1)
xgb.dump(model = temp_model, # Modèle à enregistrer
fname = paste0(file_tag, "xgb_dt_", sprintf("%02d", i), ".json"), # Où enregistrer le modèle ?
with_stats = TRUE, # Enregistrement des statistiques si modèle gbtree
dump_format = "json") # Dump au format json, ré-utilisable
accuracy[i, 3] <- 1 - temp_model$evaluation_log[[2]][1] # Récupération du meilleur résultat
# Entrainement du modèle de Random Forest (xgboost)
temp_model <- xgb_dynamic_train(train = training_xgb[[i]],
test = testing_xgb[[i]],
booster = "gbtree", # Non-linéaire
nrounds = 1, # Une seule itération
num_parallel_trees = 200) # De 200 arbres
xgb.dump(model = temp_model, # Modèle à enregistrer
fname = paste0(file_tag, "xgb_rf_", sprintf("%02d", i), ".json"), # Où enregistrer le modèle ?
with_stats = TRUE, # Enregistrement des statistiques si modèle gbtree
dump_format = "json") # Dump au format json, ré-utilisable
accuracy[i, 4] <- 1 - temp_model$evaluation_log[[2]] # Récupération du meilleur résultat
# Entrainement du modèle d'arbre de décision boosté avec protection contre l'overfitting (xgboost)
temp_model <- xgb_dynamic_train(train = training_xgb[[i]],
test = testing_xgb[[i]],
booster = "gbtree", # Non-linéaire
nrounds = 1000000, # Arrêté au meilleur résultat
num_parallel_trees = 1)
xgb.dump(model = temp_model, # Modèle à enregistrer
fname = paste0(file_tag, "xgb_gbt_", sprintf("%02d", i), ".json"), # Où enregistrer le modèle ?
with_stats = TRUE, # Enregistrement des statistiques si modèle gbtree
dump_format = "json") # Dump au format json, ré-utilisable
accuracy[i, 5] <- 1 - temp_model$evaluation_log[[2]][temp_model$best_iteration] # Récupération du meilleur résultat
# Entrainement du modèle de régression logistique (h2o)
temp_model <- h2o.glm(y = 1,
training_frame = training_h2o[[i]],
validation_frame = testing_h2o[[i]],
model_id = paste0("h2o_glm_", sprintf("%02d", i)), # Nom du modèle
max_iterations = 100, # 100 itérations d'optimisation
solver = "IRLSM", # Solveur par défaut
standardize = FALSE, # Pas de standardisation puisque [-1, 1]
family = "multinomial", # Classification multi-classe
seed = 0, # Reproduction des résultats
intercept = TRUE)
h2o.download_pojo(temp_model, # Modèle à enregistrer
path = file_h2o, # Où enregistrer le modèle ?
get_jar = FALSE) # Pas de fichier .jar
accuracy[i, 6] <- temp_model@model$validation_metrics@metrics$hit_ratio_table[1, 2]
# Entrainement du modèle d'arbre de décision (h2o)
temp_model <- h2o.randomForest(y = 1,
training_frame = training_h2o[[i]],
validation_frame = testing_h2o[[i]],
model_id = paste0("h2o_dt_", sprintf("%02d", i)), # Nom du modèle
sample_rate = 1, # Toutes les observations seront prises en compte pour le seul arbre de décision
mtries = 36, # Toutes les features seront prises en compte pour le seul arbre de décision
ntrees = 1, # Un seul arbre
seed = 0) # Reproduction des résultats
h2o.download_pojo(temp_model, # Modèle à enregistrer
path = file_h2o, # Où enregistrer le modèle ?
get_jar = FALSE) # Pas de fichier .jar
accuracy[i, 7] <- 1 - min(temp_model@model$scoring_history$validation_classification_error, na.rm = TRUE)
# Entrainement du modèle de Random Forest (h2o)
temp_model <- h2o.randomForest(y = 1,
training_frame = training_h2o[[i]],
validation_frame = testing_h2o[[i]],
model_id = paste0("h2o_rf_", sprintf("%02d", i)), # Nom du modèle
sample_rate = 0.632, # Bootstrapping .632 pour chaque arbre de décision
mtries = -1, # sqrt(36) features seront prises en compte pour chaque arbre de décision
ntrees = 200, # 200 arbres
seed = 0) # Reproduction des résultats
h2o.download_pojo(temp_model, # Modèle à enregistrer
path = file_h2o, # Où enregistrer le modèle ?
get_jar = FALSE) # Pas de fichier .jar
accuracy[i, 8] <- 1 - min(temp_model@model$scoring_history$validation_classification_error, na.rm = TRUE)
# Entrainement du modèle d'arbre de décision boosté avec protection contre l'overfitting (h2o)
temp_model <- h2o.gbm(y = 1,
training_frame = training_h2o[[i]],
validation_frame = testing_h2o[[i]],
model_id = paste0("h2o_gbt_", sprintf("%02d", i)), # Nom du modèle
distribution = "multinomial", # Classification multi-classe
sample_rate = 1, # Pas de processus stochastique
ntrees = 100, # 100 itérations de boosting au maximum
score_each_iteration = TRUE, # Noter la valeur de chaque itération
stopping_rounds = 10, # Arrêt après 10 itérations sans amélioraton de la métrique
stopping_metric = "misclassification", # Surveiller l'inexactitude de la classification pour l'arrêt
stopping_tolerance = 0.00001, # Arrêter lorsque la métrique stagne de 0.001%
seed = 0) # Reproduction des résultats
h2o.download_pojo(temp_model, # Modèle à enregistrer
path = file_h2o, # Où enregistrer le modèle ?
get_jar = FALSE) # Pas de fichier .jar
accuracy[i, 9] <- 1 - min(temp_model@model$scoring_history$validation_classification_error, na.rm = TRUE)
# Entrainement du réseau de neurones à architecture 32x6 + ReLU (h2o)
temp_model <- h2o_nn_train(train = training_h2o[[i]],
test = testing_h2o[[i]],
model_id = paste0("h2o_nn_32x6_ReLU_", sprintf("%02d", i)), # Nom du modèle
activation = "Rectifier", # ReLU
hidden = 32) # Architecture 32x6
h2o.download_pojo(temp_model, # Modèle à enregistrer
path = file_h2o, # Où enregistrer le modèle ?
get_jar = FALSE) # Pas de fichier .jar
accuracy[i, 10] <- 1 - min(temp_model@model$scoring_history$validation_classification_error, na.rm = TRUE)
# Entrainement du réseau de neurones à architecture 32x6 + Tanh (h2o)
temp_model <- h2o_nn_train(train = training_h2o[[i]],
test = testing_h2o[[i]],
model_id = paste0("h2o_nn_32x6_Tanh_", sprintf("%02d", i)), # Nom du modèle
activation = "Tanh", # "Sigmoide"
hidden = 32) # Architecture 32x6
h2o.download_pojo(temp_model, # Modèle à enregistrer
path = file_h2o, # Où enregistrer le modèle ?
get_jar = FALSE) # Pas de fichier .jar
accuracy[i, 11] <- 1 - min(temp_model@model$scoring_history$validation_classification_error, na.rm = TRUE)
# Entrainement du réseau de neurones à architecture 16x16x6 + ReLU (h2o)
temp_model <- h2o_nn_train(train = training_h2o[[i]],
test = testing_h2o[[i]],
model_id = paste0("h2o_nn_16x16x6_ReLU_", sprintf("%02d", i)), # Nom du modèle
activation = "Rectifier", # ReLU
hidden = c(16, 16)) # Architecture 16x16x6
h2o.download_pojo(temp_model, # Modèle à enregistrer
path = file_h2o, # Où enregistrer le modèle ?
get_jar = FALSE) # Pas de fichier .jar
accuracy[i, 12] <- 1 - min(temp_model@model$scoring_history$validation_classification_error, na.rm = TRUE)
# Entrainement du réseau de neurones à architecture 16x16x6 + Tanh (h2o)
temp_model <- h2o_nn_train(train = training_h2o[[i]],
test = testing_h2o[[i]],
model_id = paste0("h2o_nn_16x16x6_Tanh_", sprintf("%02d", i)), # Nom du modèle
activation = "Tanh", # "Sigmoide"
hidden = c(16, 16)) # Architecture 16x16x6
h2o.download_pojo(temp_model, # Modèle à enregistrer
path = file_h2o, # Où enregistrer le modèle ?
get_jar = FALSE) # Pas de fichier .jar
accuracy[i, 13] <- 1 - min(temp_model@model$scoring_history$validation_classification_error, na.rm = TRUE)
}
# Temps nécessaire
timing(CurrentTime, "Création et évaluation des douze modèles de benchmark final")
Temps d'exécution de la tâche 'Création et évaluation des douze modèles de benchmark final' : 183.56 secondes.
Affichage des résultats
Les résultats de la performance des modèles entrainés sont ci-dessous. Les performances semblent légèrement en hausse (de 2% environ). En revanche, les performances sont plus faibles sur lorsque les données sont validées sur la salle 1.
# Moyenne des résultats
for (i in 2:13) {
accuracy[13, i] <- mean(accuracy[1:6, i])
accuracy[14, i] <- mean(accuracy[7:9, i])
accuracy[15, i] <- mean(accuracy[10:12, i])
accuracy[16, i] <- mean(accuracy[13:15, i])
}
# Enregistrement des scores
fwrite(accuracy, "scores/3_models.csv")
# Affichage des résultats dans un tableau interactif
to_print <- data.table(t(accuracy[13:16, -1])) # Préparation des données à mettre sur table
colnames(to_print) <- c("1 contre 1", "1 contre 2", "2 contre 1", "Moyenne") # Remise des noms des colonnes
row.names(to_print) <- colnames(accuracy)[-1] # Remise des noms des lignes
datatable(to_print,
filter = "top", # Filtrage au-dessus de la table
class = "cell-border stripe", # CSS
extensions = c("ColReorder",
"RowReorder"), # Reordonner manuellement à la main
options = list(pageLength = 12, # Page affichant 12 lignes
order = list(list(4, "desc")), # Ordonner par défaut par l'exactitude moyenne
colReorder = TRUE, # Plugin
rowReorder = TRUE)) %>% # Plugin
formatStyle(c("1 contre 1", "1 contre 2", "2 contre 1"),
background = styleColorBar(c(0, 1), 'lightgreen'), # Couleur vert clair pour les métriques par fold
backgroundSize = '100% 90%',
backgroundRepeat = 'no-repeat',
backgroundPosition = 'center') %>%
formatStyle("Moyenne",
background = styleColorBar(c(0, 1), 'pink'), # Couleur rose pour la métrique de moyenne
backgroundSize = '100% 90%',
backgroundRepeat = 'no-repeat',
backgroundPosition = 'center') %>%
formatPercentage(columns = c("1 contre 1", "1 contre 2", "2 contre 1"),
digits = 8) %>%
formatPercentage(columns = "Moyenne",
digits = 8)
# Affichage des résultats dans un tableau statique
formattable(accuracy[, c(1, 2:5)], list(formattable::area(col = xgb_LinearModel:xgb_GradientBoosting) ~ color_bar("orange")))
formattable(accuracy[, c(1, 6:9)], list(formattable::area(col = h2o_LinearModel:h2o_GradientBoosting) ~ color_bar("cyan")))
formattable(accuracy[, c(1, 10:13)], list(formattable::area(col = h2o_NN_32x6_ReLU:h2o_NN_16x16x6_Soft) ~ color_bar("yellow")))
Analyse du modèle de régression logistique
Les coefficients affectés selon les échantillons d’entrainement semblent moins stables qu’auparavant, peut être un problème d’échelle (ou de VIF entre les variables).
On remarque que le chemin 2 est bien mieux classé qu’auparavant, mais que la salle 3 est toujours difficile à classer (on est passé de 12% d’exactitude à 24%, ce qui est deux fois plus précis).
En revanche, la salle 4 semble souffir (40% d’exactitude), ce qui n’étais pas le cas avec l’approximation des signaux des ancres (55% d’exactitude).
# Compteur de temps
CurrentTime <- timer() # Chunk Préparation de l'analyse du modèle de régression logistique final
# Pré-initialisation des variables
predictedValues <- matrix(nrow = 314, ncol = 6)
evolution <- list()
temp_dt <- list()
temp_means <- data.frame(Feature = c(paste0(rep(c(paste0("Coef", 1:4), paste0("Rési", 1:4)), 4), paste0("_", inverse.rle(list(lengths = rep(8, 4), values = 1:4)))), paste0("PosInitiale_", 1:4)),
Fold_1 = numeric(36),
Fold_2 = numeric(36),
Fold_3 = numeric(36),
Fold_Mean = numeric(36),
Feature_Mean = numeric(36),
Feature_SD = numeric(36))
# Boucle d'entrainement 2 contre 1
for (i in 10:12) {
# Entrainement d'un modèle linéaire
temp_model <- xgb.train(data = training_xgb[[i]],
num_class = 6, # Classification à 6 classes
nthread = 1, # 1 coeur utilisé
nrounds = 1000000, # Nombre d'itérations de boosting
eta = 0.10, # Shrinkage pour le boosting
booster = "gblinear", # Type d'entrainement : linéaire ou non-linéaire
objective = "multi:softprob", # Gradient/Hessian pour l'optimisation par Gradient Descent
eval_metric = "merror", # Inexactitude de la classification
maximize = FALSE, # Minimisation de l'erreur
early_stopping_rounds = 100, # Arrêt après 100 itérations sans amélioration de la métrique
verbose = FALSE, # Sans print des itérations
watchlist = list(test = testing_xgb[[i]]), # Estimation sur les données de test
callbacks = list(cb.evaluation.log())) # Logging des données d'entrainement pour pouvoir récupérer les métriques
# Enregistrement du log
evolution[[i - 9]] <- cbind(temp_model$evaluation_log, Fold = rep(13 - i, temp_model$niter))
# Entrainement du meilleur modèle (obtention des meilleurs coefficients)
temp_model <- xgb.train(data = training_xgb[[i]],
num_class = 6, # Classification à 6 classes
nthread = 1, # 1 coeur utilisé
nrounds = temp_model$best_iteration, # Nombre d'itérations de boosting
eta = 0.10, # Shrinkage pour le boosting
booster = "gblinear", # Type d'entrainement : linéaire ou non-linéaire
objective = "multi:softprob", # Gradient/Hessian pour l'optimisation par Gradient Descent
eval_metric = "merror", # Inexactitude de la classification
maximize = FALSE, # Minimisation de l'erreur
early_stopping_rounds = 99999, # Sans arrêt
verbose = FALSE, # Sans print des itérations
watchlist = list(test = testing_xgb[[i]]), # Estimation sur les données de test
callbacks = list(cb.evaluation.log())) # Logging des données d'entrainement pour pouvoir récupérer les métriques
# Prédiction du modèle linéaire
predictedValues[folds_test[[i]], ] <- t(matrix(predict(temp_model, testing_xgb[[i]], ntreelimit = 0), nrow = 6))
# Calcul et formattage de l'importance des variables
temp_importance <- data.table(Feature = temp_means[["Feature"]],
matrix(xgb.importance(model = temp_model)$Weight, ncol = 6))
colnames(temp_importance) <- c("Feature", paste0("Label_", 1:6))
temp_importance[["Sign"]] <- paste0(ifelse(temp_importance[[2]] >= 0, "+", "-"), ifelse(temp_importance[[3]] >= 0, "+", "-"), ifelse(temp_importance[[4]] >= 0, "+", "-"), ifelse(temp_importance[[5]] >= 0, "+", "-"), ifelse(temp_importance[[6]] >= 0, "+", "-"), ifelse(temp_importance[[7]] >= 0, "+", "-"))
temp_importance[, 2:7] <- abs(temp_importance[, 2:7, with = FALSE])
temp_means[[14 - i]] <- rowMeans(temp_importance[, 2:7, with = FALSE])
temp_importance[[paste0("Fold_", 13 - i, "_Mean")]] <- temp_means[[14 - i]]
# Enregistrement sous forme de tableau interactif
temp_dt[[i - 9]] <- datatable(temp_importance,
filter = "top", # Filtrage au-dessus de la table
class = "cell-border stripe", # CSS
extensions = c("ColReorder",
"RowReorder"), # Reordonner manuellement à la main
options = list(order = list(list(9, "desc")), # Ordonner par défaut par les facteurs ayant le poids le plus gros
colReorder = TRUE, # Plugin
rowReorder = TRUE)) %>% # Plugin
formatStyle(paste0("Label_", 1:6),
background = styleColorBar(range(temp_importance[, 2:7, with = FALSE]), 'lightblue'), # Couleur bleue pour le coefficient
backgroundSize = '100% 90%',
backgroundRepeat = 'no-repeat',
backgroundPosition = 'center') %>%
formatStyle(paste0("Fold_", 13 - i, "_Mean"),
background = styleColorBar(range(temp_importance[[9]]), 'pink'), # Couleur rose pour la métrique de moyenne
backgroundSize = '100% 90%',
backgroundRepeat = 'no-repeat',
backgroundPosition = 'center') %>%
formatRound(columns = c(paste0("Label_", 1:6), paste0("Fold_", 13 - i, "_Mean")),
digits = 6)
}
# Calcul du poids moyen affecté à chaque feature
temp_means[[5]] <- rowMeans(temp_means[, 2:4]) # Poids moyen
temp_means[[6]] <- apply(mini_lm, 2, function(x) {mean(x)}) # Moyenne de la feature dans les données
temp_means[[7]] <- apply(mini_lm, 2, function(x) {sd(x)}) # Ecart-type de la feature dans les données
# Prépration du tableau interactif sur les poids moyens agrégés
temp_dt[[4]] <- datatable(temp_means,
filter = "top", # Filtrage au-dessus de la table
class = "cell-border stripe", # CSS
extensions = c("ColReorder",
"RowReorder"), # Reordonner manuellement à la main
options = list(order = list(list(5, "desc")), # Ordonner par défaut par les facteurs ayant le poids le plus gros en moyenne
colReorder = TRUE, # Plugin
rowReorder = TRUE)) %>% # Plugin
formatStyle(paste0("Fold_", 1:3),
background = styleColorBar(range(temp_means[, 2:4]), 'lightblue'), # Couleur bleue pour le coefficient
backgroundSize = '100% 90%',
backgroundRepeat = 'no-repeat',
backgroundPosition = 'center') %>%
formatStyle("Fold_Mean",
background = styleColorBar(range(temp_means[[5]]), 'pink'), # Couleur rose pour la métrique de moyenne
backgroundSize = '100% 90%',
backgroundRepeat = 'no-repeat',
backgroundPosition = 'center') %>%
formatStyle("Feature_Mean",
background = styleColorBar(range(temp_means[[6]]), 'lightgreen'), # Couleur verte pour la moyenne des features
backgroundSize = '100% 90%',
backgroundRepeat = 'no-repeat',
backgroundPosition = 'center') %>%
formatStyle("Feature_SD",
background = styleColorBar(range(temp_means[[7]]), 'orange'), # Couleur verte pour l'écart-type des features
backgroundSize = '100% 90%',
backgroundRepeat = 'no-repeat',
backgroundPosition = 'center') %>%
formatRound(columns = c(paste0("Fold_", 1:3), "Fold_Mean", "Feature_Mean", "Feature_SD"),
digits = 6)
# Dépivotage du log
evolution <- rbindlist(evolution)
colnames(evolution) <- c("Iteration", "Exactitude", "Fold")
evolution$Exactitude <- 1 - evolution$Exactitude
evolution$Fold <- as.factor(evolution$Fold)
# Prédiction à partir des probabilités
predictedLabel <- data.frame(Label = group_path$path_ID, Prediction = apply(predictedValues, 1, function(x) {which.max(x)}))
timing(CurrentTime, "Préparation de l'analyse du modèle de régression logistique final")
Temps d'exécution de la tâche 'Préparation de l'analyse du modèle de régression logistique final' : 6.66 secondes.
# Affichage de l'évolution de la performance du modèle selon le nombre d'itération, sous forme de plot interactif
ggplotly(ggplot(data = evolution, aes_string(x = "Iteration", y = "Exactitude", group = "Fold", color = "Fold")) + geom_line() + geom_point() + scale_color_brewer(palette = "Set2") + theme_bw() + labs(title = "Evolution de l'exactitude par rapport au nombre d'itérations d'entrainement"), width = 960, height = 720)
# Affichage de la matrice de confusion sous forme de plot interactif
confusion_mat <- expand.grid(Label = 1:6, Prediction = 1:6)
confusion_mat <- merge(confusion_mat, data.table(predictedLabel)[, list(Freq = sum(.N)), by = list(Label, Prediction)], by = c("Label", "Prediction"), all.x = TRUE)
confusion_mat[["Freq"]][is.na(confusion_mat[["Freq"]])] <- 0
ggplotly(ggplot() + geom_rect(data = data.frame(cent = 1:6), size = 2, fill = NA, colour = "black", aes(xmin = cent - 0.5, xmax = cent + 0.5, ymin = cent - 0.5, ymax = cent + 0.5)) + geom_tile(data = confusion_mat, aes_string(x = "Label", y = "Prediction", fill = "Freq")) + geom_text(data = confusion_mat, aes_string(x = "Label", y = "Prediction", label = "Freq")) + scale_x_discrete(name = "Trajectoire Réelle") + scale_y_discrete(name = "Trajectoire Prédite") + scale_fill_gradientn(colours = rev(brewer.pal_extended(3, "PiYG"))) + labs(title = "Matrice de Confusion de la Trajectoire", fill = "Fréquence"), width = 960, height = 720)
# Affichage des tables à la fin car le formattage possède un bug inhérent lorsqu'on a plusieurs datatables (DT) dans le même chunk
# htmltools::tagList(temp_dt[[3]], temp_dt[[2]], temp_dt[[1]], temp_dt[[4]])
Optimisation de la régression logistique
Il est tout à fait possible d’optimiser la régression logistique, par trois chemins :
- Optimiser les hyperparamètres
- Sélectionner le meilleur subset de features à utiliser
- Utiliser de meilleures features
Par manque de temps, nous ne travaillerons pas sur l’élaboration de meilleures features. A la place, nous optimiserons les hyperparamètres et les features sélectionnées.
Optimisation par entropie croisée
Nous allons utiliser l’optimisation par entropie croisée (Cross-Entropy Optimization), qui donne des résultats remarquables dans la quasi intégralité des cas (à moins que toutes les features et tous les hyperparamètres soient déjà l’un des meilleurs possibles). Grâce à cette méthode, nous pouvons :
- Optimiser les hyperparamètres de nos choix, qu’ils soient continus ou discrets
- Sélectionner des features (discrétisation binaire pour la sélection)
Ici, nous avons trois hyperparamètres et 36 features à sélectionner :
- alpha : régularisation L1 sur les coefficients, qu’on va constraindre entre 0 et 5
- lambda : régularisation L2 sur les coefficients, qu’on va constraindre entre 0 et 5
- lambda_bias : régularisation L2 sur le biais, qu’on va constraindre entre 0 et 5
- 36 features : on souhaite réduire de 72% environ le nombre de features pour que le modèle final soit performant et simple à comprendre (environ 10 features)
Pour ne pas que l’optimiseur converge trop rapidement, nous allons utiliser ces paramètres d’optimisation :
- optimisation du boosting : nous allons utiliser la perte logarithmique et non pas l’inexactitude pour interrompre le boosting, afin d’éviter un effet de chance et faire converger plus rapidement chaque modèle
- valeur à optimiser : minimisation de la perte logarithmique de classification (logloss) et non pas de l’inexactitude de classification afin d’éviter l’overfitting involontaire ou l’effet de chance due à la faible quantité d’observations (mais également pour fournir une échelle continue et pas discrète à l’optimiseur pour la perte) - pour tenter d’optimiser l’exactitude, on multiplie l’inexactitude par la perte logarithmique qui sera passée comme valeur à optimiser par entropie croisée
- échantillons par itérations : 250 modèles pour une population stable et diverse
- elites : 10% d’elites pour avoir une population plutôt stable et diverse
- nombre d’itérations : 20 pour laisser à l’optimiseur le temps de chercher (mais très rapidement), avec arrêt prématuré en cas de stagnation de l’optimisation pendant 5 itérations
- optimisation des valeurs continues : convergence lorsque les hyperparamètres ont chacun un écart-type en-dessous de 0.10 dans la population élite
- optimisation des valeurs discrètes : convergence lorsque la sélection des features est identique dans la population élite
Toutes les variables optimisées seront loggées et leurs évolutions seront visibles en temps réel via divers logiciels (exemple : BareTail), avec le tag “***" lorsque l’optimisation donne un nouveau minimum local.
Une recherche exhaustive ne fonctionnera pas même avec un petit subset de features recherchées : nous sommes en présence d’hyperparamètres continus et non discrets.
A la fin, nous avons 13 features avec une meilleure performance que celle du modèle initiale (jusqu’à 72% d’exactitude contre 65% initialement). Notre modèle nécessite donc plus de features qu’on souhaitait (36% des features au lieu de 28%), mais le gain en performance est majeur (+7%). On peut analyser le log pour extraire la meilleure combinaison qui nous permet de conserver uniquement des 10 features, mais on ne le fera pas par manque de temps ici.
# Compteur de temps
CurrentTime <- timer() # Optimisation par entropie croisée
CE_Features <- function(x, y, train, test) {
# Pré-initialisation de certaines variables
to_keep <- as.boolean(y)
iters <<- iters + 1
# Au moins une feature ?
if (sum(to_keep) > 0) {
error <- numeric(3)
lloss <- numeric(3)
for (i in 10:12) {
temp_model <- xgb.train(data = xgb.DMatrix(data = as.matrix(train[[i]][, which(to_keep)]), label = train[[i]][["Label"]]), # Données d'entrainement
watchlist = list(test = xgb.DMatrix(data = as.matrix(test[[i]][, which(to_keep)]), label = test[[i]][["Label"]])), # Données de validation
num_class = 6, # 6 classe de classification
nthread = 1, # 1 thread pour la reproduction des résultats
nrounds = 1000, # 1000 itérations, où arrêt prématuré
alpha = x[1], # Régularisation L1 (Lasso)
lambda = x[2], # Régularisation L2 (Ridge)
lambda_bias = x[3], # Régularisation L2 du biais (Ridge)
eta = 0.10, # Shrinkage pour le boosting
booster = "gblinear", # Type d'entrainement : linéaire ou non-linéaire
objective = "multi:softprob", # Gradient/Hessian pour l'optimisation par Gradient Descent
eval_metric = "mlogloss", # Perte logiarthmique de la classification (cette métrique est optimisée par xgboost)
eval_metric = "merror", # Inexactitude de la classification
maximize = FALSE, # Minimisation de l'erreur
early_stopping_rounds = 50, # Arrêt après 50 itérations sans amélioration de la métrique
verbose = FALSE, # Sans print des itérations
callbacks = list(cb.evaluation.log())) # Logging des données d'entrainement pour pouvoir récupérer les métriques)
error[13 - i] <- temp_model$evaluation_log$test_merror[temp_model$best_iteration] # Enregistrement du meilleur score (inexactitude)
lloss[13 - i] <- temp_model$evaluation_log$test_mlogloss[temp_model$best_iteration] # Enregistrement du meilleur score (perte logarithmique)
}
# Enregistrement de l'erreur et logging dans l'environnemnt global
error_list[iters, 2:4] <<- error # Inexactitude
error <- mean(error) # Inexactitude moyenne
error_list[iters, 5] <<- error # Inexactitude moyenne
error_list[iters, 6:8] <<- lloss # Perte logarithmique
lloss <- mean(lloss) # Perte logarithmique moyenne
error_list[iters, 9] <<- lloss # Perte logarithmique moyenne
score <- error * lloss # Score de perte
error_list[iters, 10] <<- score # Score de perte
error_list[iters, 11] <<- sum(to_keep) # Compte de features
error_list[iters, 12:14] <<- as.numeric(x) # Hyperparamètres
error_list[iters, 15:50] <<- as.numeric(to_keep) # Features utilisées
error_list[iters, 51] <<- 1
# Le résultat est-il meilleur ? (pour le logging en temps réel)
if (error < best_error) {
best_error <<- error
star <- "(*** - "
} else {
star <- "( - "
}
if (lloss < best_lloss) {
best_lloss <<- lloss
star <- paste0(star, "*** - ")
} else {
star <- paste0(star, " - ")
}
if (score < best_score) {
best_score <<- score
star <- paste0(star, "***) ")
} else {
star <- paste0(star, " ) ")
}
# Logging en temps réel
cat(star, "[", format(Sys.time(), "%X"), "] Pass ", sprintf("%05d", iters), ": Error=", sprintf("%.05f", error), " - Loss=", sprintf("%.07f", lloss), " - Score=", sprintf("%.07f", score), " - feats=", sprintf("%04d", sum(to_keep)), " - alpha=", sprintf("%07.05f", x[1]), ", lambda=", sprintf("%07.05f", x[2]), ", lambda_bias=", sprintf("%07.05f", x[3]), "\n", sep = "", file = "optim/log.txt", append = TRUE)
return(score)
} else {
# Logging en temps réel
cat("( - - ) [", format(Sys.time(), "%X"), "] Pass ", sprintf("%05d", iters), ": failed\n", sep = "", file = "optim/log.txt", append = TRUE)
return(9.9999)
}
}
# Où sauvegarder les fichiers ?
file_tag <- "4_data/"
# Création des données d'entrainement et de validation
for (i in 1:12) {
# Création des données d'entrainement et de validation
training_data[[i]][["Label"]] <- group_path$path_ID[folds_train[[i]]] - 1
testing_data[[i]][["Label"]] <- group_path$path_ID[folds_test[[i]]] - 1
}
# Paramètres de l'optimiseur
cont_opt <- list(mean = c(1, 1, 1), # Débute avec en moyenne, Alpha=1, Lambda=1, Lambda_bias=1
sd = c(1, 1, 1), # Débute avec en écart-type, Alpha=1, Lambda=1, Lambda_bias=1
conMat = rbind(diag(3), -diag(3)), # Optimisation linéaire conditionnée par la matrice du simplexe
conVec = c(5, 5, 5, 0, 0, 0), # 0<=alpha<=5, 0<=Lambda<=5, 0<=Lambda_bias<=5
sdThr = 0.1) # On suppose les hyperparamètres convergés lorsque tous les écart-types sont en-dessous de 0.1
p0 <- list() # Pré-initaisliation de la liste pour les variables discrètes
for (i in 1:36) {p0 <- c(p0, list(c(0.72, 0.28)))} # On souhaite 50% des features à la fin en moyenne, à moins que certaines variables ont une importance telle qu'elles ne peuvent être omises et seront forcément sélectionnées
disc_opt <- list(probs = p0,
smoothProb = 1.00, # On va tenter de converger rapidement ici pour la réalistion d'un proof of concept, mais sinon avec plus de temps on pourra réaliser un shrinkage de 5% de la probabilité élite à chaque itération (smoothProb = 0.95)
probThr = 0.0001) # On suppose la sélection de features convergé lorsque toutes les probabilités sont en-dessous de 0.0001
n_family <- 250 # Le nombre d'estimations par itération de l'optimiseur
elite <- 0.1 # Le nombre d'élites par itération qui dictent la loi dans l'entropie croisée
iterations <- 21 # Le nombre d'itérations d'optimisation (plus un pour l'itération d'initialisation)
early_stop <- 5 # Arrêt prématuré lorsque la fonction de perte (ici l'inexactitude de la classification) ne diminue pas après X itérations
# Pré-initialisation de la variable de logging
iters <- 0 # Suivi de l'itération
best_error <- 1 # Suivi de la perte (inexactitude), initialisé à une très mauvaise valeur possible
best_lloss <- 9.9999 # Suivi de la perte (logarithmique), initialisé à une très mauvaise valeur possible
best_score <- 9.9999 # Suivi de la perte (inexactitude * logarithmique), initialisé à une très mauvaise valeur possible
error_list <- data.frame(Iteration = 1:(n_family * iterations),
Error_1 = numeric(n_family * iterations),
Error_2 = numeric(n_family * iterations),
Error_3 = numeric(n_family * iterations),
Error_Mean = numeric(n_family * iterations),
Loss_1 = numeric(n_family * iterations),
Loss_2 = numeric(n_family * iterations),
Loss_3 = numeric(n_family * iterations),
Loss_Mean = numeric(n_family * iterations),
Score = numeric(n_family * iterations),
Features_n = numeric(n_family * iterations),
Alpha = numeric(n_family * iterations),
Lambda = numeric(n_family * iterations),
Lambda_bias = numeric(n_family * iterations),
matrix(rep(0, n_family * iterations * 36), ncol = 36),
Logging = rep(0, n_family * iterations))
colnames(error_list)[15:50] <- c(paste0(rep(c(paste0("Coef", 1:4), paste0("Rési", 1:4)), 4), paste0("_", inverse.rle(list(lengths = rep(8, 4), values = 1:4)))), paste0("PosInitiale_", 1:4))
set.seed(0) # Fixation du seed aléatoire pour des résultats qui puissent être reproduits
# Optimisation par entropie croisée
best_weights <- CEoptim(CE_Features,
f.arg = list(train = training_data, # Données d'entrainement
test = testing_data), # Données de validation
maximize = FALSE, # Minimisation du problème
continuous = cont_opt,
discrete = disc_opt,
N = n_family,
rho = elite,
verbose = TRUE,
iterThr = iterations - 1,
noImproveThr = early_stop)
Number of continuous variables: 3
Number of discrete variables: 36
conMat=
[,1] [,2] [,3]
[1,] 1 0 0
[2,] 0 1 0
[3,] 0 0 1
[4,] -1 0 0
[5,] 0 -1 0
[6,] 0 0 -1
conVec=
[1] 5 5 5 0 0 0
smoothMean: 1 smoothSd: 1 smoothProb: 1
N: 250 rho: 0.1 iterThr: 20 sdThr: 0.1 probThr 1e-04
iter: 0 opt: 0.3749418 maxSd: 0.9782853 maxProbs: 0.48
iter: 1 opt: 0.3386276 maxSd: 0.8361778 maxProbs: 0.48
iter: 2 opt: 0.3114675 maxSd: 0.56788 maxProbs: 0.48
iter: 3 opt: 0.3067754 maxSd: 0.573345 maxProbs: 0.44
iter: 4 opt: 0.291244 maxSd: 0.6706154 maxProbs: 0.48
iter: 5 opt: 0.290563 maxSd: 0.5828973 maxProbs: 0.48
iter: 6 opt: 0.2811712 maxSd: 0.6613192 maxProbs: 0.44
iter: 7 opt: 0.2793675 maxSd: 0.5748965 maxProbs: 0.48
iter: 8 opt: 0.2741055 maxSd: 0.5865809 maxProbs: 0.48
iter: 9 opt: 0.2714183 maxSd: 0.4431069 maxProbs: 0.36
iter: 10 opt: 0.2714183 maxSd: 0.3804242 maxProbs: 0.24
iter: 11 opt: 0.2700002 maxSd: 0.3880721 maxProbs: 0.16
iter: 12 opt: 0.2700002 maxSd: 0.3490389 maxProbs: 0.08
iter: 13 opt: 0.2700002 maxSd: 0.4694499 maxProbs: 0
iter: 14 opt: 0.2700002 maxSd: 0.5475662 maxProbs: 0
iter: 15 opt: 0.2700002 maxSd: 0.6524463 maxProbs: 0
iter: 16 opt: 0.2699669 maxSd: 0.5058152 maxProbs: 0
iter: 17 opt: 0.26996 maxSd: 0.4172189 maxProbs: 0
iter: 18 opt: 0.269868 maxSd: 0.4959221 maxProbs: 0
iter: 19 opt: 0.2698675 maxSd: 0.5982446 maxProbs: 0
# Enregistrement du log détaillé
fwrite(error_list, "optim/error_raw.csv")
# Enregistrement du log détaillé et nettoyé des éléments inutiles
error_list <- error_list[error_list$Logging == 1, ]
fwrite(error_list, "optim/error_clean.csv")
# Enregistrement de la varible contenant l'optimisation
saveRDS(best_weights, "optim/optimized.rds")
# Affichage des résultats
x <- best_weights$optimizer$continuous # Récupération des hyperparamètres
y <- best_weights$optimizer$discrete # Récupération des features sélectionnées
cat(" \nL'optimiseur a trouvé : \n - Meilleure Inexactitude = ", best_error, " \n - Meilleure Perte Logarithmique = ", best_lloss, "\n - alpha = ", x[1], " \n - lambda = ", x[2], " \n - lambda_bias = ", x[3], " \n - features = ", sum(as.boolean(y)), " (binary = ", paste(y, collapse = ""), ") \n \nFeatures utilisées : \n", sep = "")
L'optimiseur a trouvé :
- Meilleure Inexactitude = 0.2838657
- Meilleure Perte Logarithmique = 0.9425253
- alpha = 0.3762587
- lambda = 0.221014
- lambda_bias = 0.6638027
- features = 13 (binary = 000010000001001011111010001100000110)
Features utilisées :
dput(c(paste0(rep(c(paste0("Coef", 1:4), paste0("Rési", 1:4)), 4), paste0("_", inverse.rle(list(lengths = rep(8, 4), values = 1:4)))), paste0("PosInitiale_", 1:4))[as.boolean(y)]) # Affichage des noms des features
c("Rési1_1", "Coef4_2", "Rési3_2", "Coef1_3", "Coef2_3", "Coef3_3",
"Coef4_3", "Rési1_3", "Rési3_3", "Coef3_4", "Coef4_4", "PosInitiale_2",
"PosInitiale_3")
# Temps nécessaire
timing(CurrentTime, "Optimisation par entropie croisée")
Temps d'exécution de la tâche 'Optimisation par entropie croisée' : 1848.70 secondes.
Visualisation de l’évolution de l’optimisation
On peut visualiser l’évolution de l’optimisation à partir de graphiques. On remarque une convergence plutôt rapide des pertes (inexactitude, logloss).
On remarquera l’inversion entre du niveau des courbes des salles 1 et 2 lorsqu’on oppose perte logarithmique et inexactitude. En effet, il n’existe aucune corrélation directe entre la perte logarithmique et l’inexactitude.
ggplotly(ggplot(data = error_list[, c("Iteration", "Error_Mean")], aes_string(x = "Iteration", y = "Error_Mean")) + geom_line() + theme_bw() + labs(title = "Evolution de l'inexactitude par rapport au nombre d'itérations de modélisations en moyenne"), width = 960, height = 720)
ggplotly(ggplot(data = data.frame(Iteration = rep(1:nrow(error_list), 3), Error = c(error_list[["Error_1"]], error_list[["Error_2"]], error_list[["Error_3"]]), Salle = as.factor(inverse.rle(list(lengths = rep(nrow(error_list), 3), values = 1:3)))), aes_string(x = "Iteration", y = "Error", color = "Salle")) + geom_line() + theme_bw() + labs(title = "Evolution de l'inexactitude par rapport au nombre d'itérations de modélisation par salle"), width = 960, height = 720)
ggplotly(ggplot(data = error_list[, c("Iteration", "Loss_Mean")], aes_string(x = "Iteration", y = "Loss_Mean")) + geom_line() + theme_bw() + labs(title = "Evolution du logloss par rapport au nombre d'itérations de modélisations en moyenne"), width = 960, height = 720)
ggplotly(ggplot(data = data.frame(Iteration = rep(1:nrow(error_list), 3), Loss = c(error_list[["Loss_1"]], error_list[["Loss_2"]], error_list[["Loss_3"]]), Salle = as.factor(inverse.rle(list(lengths = rep(nrow(error_list), 3), values = 1:3)))), aes_string(x = "Iteration", y = "Loss", color = "Salle")) + geom_line() + theme_bw() + labs(title = "Evolution du logloss par rapport au nombre d'itérations de modélisation par salle"), width = 960, height = 720)
Création des features
Nous pouvons créer les features à partir des features sélectionnées.
# Compteur de temps
CurrentTime <- timer() # Préparation de l'évaluation des modèles avec features sélectionnées
# Où sauvegarder les fichiers ?
file_tag <- "4_data/"
# Initialisation de la variable qui accueillera la précision
accuracy <- data.frame(matrix(nrow = 16, ncol = 13))
colnames(accuracy) <- c("Fold", "xgb_LinearModel", "xgb_DecisionTree", "xgb_RandomForest", "xgb_GradientBoosting", "h2o_LinearModel", "h2o_DecisionTree", "h2o_RandomForest", "h2o_GradientBoosting", "h2o_NN_32x6_ReLU", "h2o_NN_32x6_Soft", "h2o_NN_16x16x6_ReLU", "h2o_NN_16x16x6_Soft")
accuracy[, 1] <- c("Fold_1v2", "Fold_1v3", "Fold_2v1", "Fold_2v3", "Fold_3v1", "Fold_3v2", "Fold_1v23", "Fold_2v13", "Fold_3v12", "Fold_12v3", "Fold_13v2", "Fold_23v1", "Moyenne_1c1", "Moyenne_1c2", "Moyenne_2c1", "Moyenne")
# Initialisation des folds pour la cross-validation
folds_train <- list()
folds_test <- list()
training_data <- list()
testing_data <- list()
training_xgb <- list()
testing_xgb <- list()
training_h2o <- list()
testing_h2o <- list()
combinations_train <- c(list(1, 1, 2, 2, 3, 3), combn(3, 1, simplify = FALSE), combn(3, 2, simplify = FALSE))
combinations_test <- c(list(2, 3, 1, 3, 1, 2), rev(combn(3, 2, simplify = FALSE)), rev(combn(3, 1, simplify = FALSE)))
temp_factors <- as.factor(group_path$path_ID)
# Création des données d'entrainement et de validation
for (i in 1:12) {
# Création des folds d'entrainement et de validation
folds_train[[i]] <- which(group_room[["dataset_ID"]] %in% combinations_train[[i]])
folds_test[[i]] <- which(group_room[["dataset_ID"]] %in% combinations_test[[i]])
# Recherche et suppression du label 3 lorsque la salle 1 est isolée (soit en train on enlève en test, soit en test on enlève en train)
if ((length(combinations_train[[i]]) == 1) & (combinations_train[[i]][1] == 1)) {
folds_test[[i]] <- folds_test[[i]][group_path$path_ID[folds_test[[i]]] != 3]
}
if ((length(combinations_test[[i]]) == 1) & (combinations_test[[i]][1] == 1)) {
folds_train[[i]] <- folds_train[[i]][group_path$path_ID[folds_train[[i]]] != 3]
}
# Création des données d'entrainement et de validation
training_data[[i]] <- mini_lm[folds_train[[i]], which(best_weights$optimizer$discrete == 1)]
testing_data[[i]] <- mini_lm[folds_test[[i]], which(best_weights$optimizer$discrete == 1)]
# Enregistrement des données CSV
fwrite(training_data[[i]], paste0(file_tag, "trainNL_", sprintf("%02d", i), ".csv"))
fwrite(testing_data[[i]], paste0(file_tag, "testNL_", sprintf("%02d", i), ".csv"))
# Transformation des données au format approprié pour xgboost
training_xgb[[i]] <- xgb.DMatrix(data = as.matrix(training_data[[i]]), label = group_path$path_ID[folds_train[[i]]] - 1)
testing_xgb[[i]] <- xgb.DMatrix(data = as.matrix(testing_data[[i]]), label = group_path$path_ID[folds_test[[i]]] - 1)
# Dumping des datasets binaires xgboost
xgb.DMatrix.save(training_xgb[[i]], paste0(file_tag, "trainL_", sprintf("%02d", i), ".data"))
xgb.DMatrix.save(testing_xgb[[i]], paste0(file_tag, "testL_", sprintf("%02d", i), ".data"))
# Transformation des données au format approprié pour H2O
training_h2o[[i]] <- as.h2o(cbind(Label = temp_factors[folds_train[[i]]], training_data[[i]]))
testing_h2o[[i]] <- as.h2o(cbind(Label = temp_factors[folds_test[[i]]], testing_data[[i]]))
# Enregistrement des frames H2O (CSV + Label)
h2o.exportFile(training_h2o[[i]], paste0(file_tag, "trainL_", sprintf("%02d", i), ".csv"), force = TRUE)
h2o.exportFile(testing_h2o[[i]], paste0(file_tag, "testL_", sprintf("%02d", i), ".csv"), force = TRUE)
}
# Temps nécessaire
timing(CurrentTime, "Préparation de l'évaluation des modèles avec features sélectionnées")
Temps d'exécution de la tâche 'Préparation de l'évaluation des modèles avec features sélectionnées' : 15.58 secondes.
Entrainement des douze modèles
Nous pouvons maintenant entrainer les modèles sur notre sélection de features.
# Compteur de temps
CurrentTime <- timer() # Chunk Création et évaluation des douze modèles avec features sélectionnées
# Où sauvegarder les fichiers ?
file_tag <- "4_models/"
file_h2o <- "4_models"
# Boucle d'évaluation
for (i in 1:12) {
# Entrainement du modèle de régression logistique (xgboost)
temp_model <- xgb_dynamic_train(train = training_xgb[[i]],
test = testing_xgb[[i]],
booster = "gblinear", # Linéaire
nrounds = 1000000, # Arrêté au meilleur résultat
num_parallel_trees = 1)
xgb.dump(model = temp_model, # Modèle à enregistrer
fname = paste0(file_tag, "xgb_glm_", sprintf("%02d", i), ".json"), # Où enregistrer le modèle ?
with_stats = TRUE, # Enregistrement des statistiques si modèle gbtree
dump_format = "json") # Dump au format json, ré-utilisable
accuracy[i, 2] <- 1 - temp_model$evaluation_log[[2]][temp_model$best_iteration] # Récupération du meilleur résultat
# Entrainement du modèle d'arbre de décision (xgboost)
temp_model <- xgb_dynamic_train(train = training_xgb[[i]],
test = testing_xgb[[i]],
booster = "gbtree", # Non-linéaire
nrounds = 1, # Un seul arbre
num_parallel_trees = 1)
xgb.dump(model = temp_model, # Modèle à enregistrer
fname = paste0(file_tag, "xgb_dt_", sprintf("%02d", i), ".json"), # Où enregistrer le modèle ?
with_stats = TRUE, # Enregistrement des statistiques si modèle gbtree
dump_format = "json") # Dump au format json, ré-utilisable
accuracy[i, 3] <- 1 - temp_model$evaluation_log[[2]][1] # Récupération du meilleur résultat
# Entrainement du modèle de Random Forest (xgboost)
temp_model <- xgb_dynamic_train(train = training_xgb[[i]],
test = testing_xgb[[i]],
booster = "gbtree", # Non-linéaire
nrounds = 1, # Une seule itération
num_parallel_trees = 200) # De 200 arbres
xgb.dump(model = temp_model, # Modèle à enregistrer
fname = paste0(file_tag, "xgb_rf_", sprintf("%02d", i), ".json"), # Où enregistrer le modèle ?
with_stats = TRUE, # Enregistrement des statistiques si modèle gbtree
dump_format = "json") # Dump au format json, ré-utilisable
accuracy[i, 4] <- 1 - temp_model$evaluation_log[[2]] # Récupération du meilleur résultat
# Entrainement du modèle d'arbre de décision boosté avec protection contre l'overfitting (xgboost)
temp_model <- xgb_dynamic_train(train = training_xgb[[i]],
test = testing_xgb[[i]],
booster = "gbtree", # Non-linéaire
nrounds = 1000000, # Arrêté au meilleur résultat
num_parallel_trees = 1)
xgb.dump(model = temp_model, # Modèle à enregistrer
fname = paste0(file_tag, "xgb_gbt_", sprintf("%02d", i), ".json"), # Où enregistrer le modèle ?
with_stats = TRUE, # Enregistrement des statistiques si modèle gbtree
dump_format = "json") # Dump au format json, ré-utilisable
accuracy[i, 5] <- 1 - temp_model$evaluation_log[[2]][temp_model$best_iteration] # Récupération du meilleur résultat
# Entrainement du modèle de régression logistique (h2o)
temp_model <- h2o.glm(y = 1,
training_frame = training_h2o[[i]],
validation_frame = testing_h2o[[i]],
model_id = paste0("h2o_glm_", sprintf("%02d", i)), # Nom du modèle
max_iterations = 100, # 100 itérations d'optimisation
solver = "IRLSM", # Solveur par défaut
standardize = FALSE, # Pas de standardisation puisque [-1, 1]
family = "multinomial", # Classification multi-classe
seed = 0, # Reproduction des résultats
intercept = TRUE)
h2o.download_pojo(temp_model, # Modèle à enregistrer
path = file_h2o, # Où enregistrer le modèle ?
get_jar = FALSE) # Pas de fichier .jar
accuracy[i, 6] <- temp_model@model$validation_metrics@metrics$hit_ratio_table[1, 2]
# Entrainement du modèle d'arbre de décision (h2o)
temp_model <- h2o.randomForest(y = 1,
training_frame = training_h2o[[i]],
validation_frame = testing_h2o[[i]],
model_id = paste0("h2o_dt_", sprintf("%02d", i)), # Nom du modèle
sample_rate = 1, # Toutes les observations seront prises en compte pour le seul arbre de décision
mtries = sum(best_weights$optimizer$discrete), # Toutes les features seront prises en compte pour le seul arbre de décision
ntrees = 1, # Un seul arbre
seed = 0) # Reproduction des résultats
h2o.download_pojo(temp_model, # Modèle à enregistrer
path = file_h2o, # Où enregistrer le modèle ?
get_jar = FALSE) # Pas de fichier .jar
accuracy[i, 7] <- 1 - min(temp_model@model$scoring_history$validation_classification_error, na.rm = TRUE)
# Entrainement du modèle de Random Forest (h2o)
temp_model <- h2o.randomForest(y = 1,
training_frame = training_h2o[[i]],
validation_frame = testing_h2o[[i]],
model_id = paste0("h2o_rf_", sprintf("%02d", i)), # Nom du modèle
sample_rate = 0.632, # Bootstrapping .632 pour chaque arbre de décision
mtries = -1, # sqrt(36) features seront prises en compte pour chaque arbre de décision
ntrees = 200, # 200 arbres
seed = 0) # Reproduction des résultats
h2o.download_pojo(temp_model, # Modèle à enregistrer
path = file_h2o, # Où enregistrer le modèle ?
get_jar = FALSE) # Pas de fichier .jar
accuracy[i, 8] <- 1 - min(temp_model@model$scoring_history$validation_classification_error, na.rm = TRUE)
# Entrainement du modèle d'arbre de décision boosté avec protection contre l'overfitting (h2o)
temp_model <- h2o.gbm(y = 1,
training_frame = training_h2o[[i]],
validation_frame = testing_h2o[[i]],
model_id = paste0("h2o_gbt_", sprintf("%02d", i)), # Nom du modèle
distribution = "multinomial", # Classification multi-classe
sample_rate = 1, # Pas de processus stochastique
ntrees = 100, # 100 itérations de boosting au maximum
score_each_iteration = TRUE, # Noter la valeur de chaque itération
stopping_rounds = 10, # Arrêt après 10 itérations sans amélioraton de la métrique
stopping_metric = "misclassification", # Surveiller l'inexactitude de la classification pour l'arrêt
stopping_tolerance = 0.00001, # Arrêter lorsque la métrique stagne de 0.001%
seed = 0) # Reproduction des résultats
h2o.download_pojo(temp_model, # Modèle à enregistrer
path = file_h2o, # Où enregistrer le modèle ?
get_jar = FALSE) # Pas de fichier .jar
accuracy[i, 9] <- 1 - min(temp_model@model$scoring_history$validation_classification_error, na.rm = TRUE)
# Entrainement du réseau de neurones à architecture 32x6 + ReLU (h2o)
temp_model <- h2o_nn_train(train = training_h2o[[i]],
test = testing_h2o[[i]],
model_id = paste0("h2o_nn_32x6_ReLU_", sprintf("%02d", i)), # Nom du modèle
activation = "Rectifier", # ReLU
hidden = 32) # Architecture 32x6
h2o.download_pojo(temp_model, # Modèle à enregistrer
path = file_h2o, # Où enregistrer le modèle ?
get_jar = FALSE) # Pas de fichier .jar
accuracy[i, 10] <- 1 - min(temp_model@model$scoring_history$validation_classification_error, na.rm = TRUE)
# Entrainement du réseau de neurones à architecture 32x6 + Tanh (h2o)
temp_model <- h2o_nn_train(train = training_h2o[[i]],
test = testing_h2o[[i]],
model_id = paste0("h2o_nn_32x6_Tanh_", sprintf("%02d", i)), # Nom du modèle
activation = "Tanh", # "Sigmoide"
hidden = 32) # Architecture 32x6
h2o.download_pojo(temp_model, # Modèle à enregistrer
path = file_h2o, # Où enregistrer le modèle ?
get_jar = FALSE) # Pas de fichier .jar
accuracy[i, 11] <- 1 - min(temp_model@model$scoring_history$validation_classification_error, na.rm = TRUE)
# Entrainement du réseau de neurones à architecture 16x16x6 + ReLU (h2o)
temp_model <- h2o_nn_train(train = training_h2o[[i]],
test = testing_h2o[[i]],
model_id = paste0("h2o_nn_16x16x6_ReLU_", sprintf("%02d", i)), # Nom du modèle
activation = "Rectifier", # ReLU
hidden = c(16, 16)) # Architecture 16x16x6
h2o.download_pojo(temp_model, # Modèle à enregistrer
path = file_h2o, # Où enregistrer le modèle ?
get_jar = FALSE) # Pas de fichier .jar
accuracy[i, 12] <- 1 - min(temp_model@model$scoring_history$validation_classification_error, na.rm = TRUE)
# Entrainement du réseau de neurones à architecture 16x16x6 + Tanh (h2o)
temp_model <- h2o_nn_train(train = training_h2o[[i]],
test = testing_h2o[[i]],
model_id = paste0("h2o_nn_16x16x6_Tanh_", sprintf("%02d", i)), # Nom du modèle
activation = "Tanh", # "Sigmoide"
hidden = c(16, 16)) # Architecture 16x16x6
h2o.download_pojo(temp_model, # Modèle à enregistrer
path = file_h2o, # Où enregistrer le modèle ?
get_jar = FALSE) # Pas de fichier .jar
accuracy[i, 13] <- 1 - min(temp_model@model$scoring_history$validation_classification_error, na.rm = TRUE)
}
# Temps nécessaire
timing(CurrentTime, "Création et évaluation des douze modèles avec features sélectionnées")
Temps d'exécution de la tâche 'Création et évaluation des douze modèles avec features sélectionnées' : 167.14 secondes.
Affichage des résultats
Les modèles sont bien plus performants qu’initialement après une sélection des features. On atteint désormais une moyenne haute d’exactitude de 68%, contre 63% auparavant. C’est un avancement important dans la performance des modèles, et on sait qu’on peut encore ajouter 3-4% de performance aux modèles les plus performants avec un tuning des hyperparamètres.
On remarque les mêmes modèles sont toujours les plus performants : ce sont les modèles linéaires !
# Moyenne des résultats
for (i in 2:13) {
accuracy[13, i] <- mean(accuracy[1:6, i])
accuracy[14, i] <- mean(accuracy[7:9, i])
accuracy[15, i] <- mean(accuracy[10:12, i])
accuracy[16, i] <- mean(accuracy[13:15, i])
}
# Enregistrement des scores
fwrite(accuracy, "scores/4_models.csv")
# Affichage des résultats dans un tableau interactif
to_print <- data.table(t(accuracy[13:16, -1])) # Préparation des données à mettre sur table
colnames(to_print) <- c("1 contre 1", "1 contre 2", "2 contre 1", "Moyenne") # Remise des noms des colonnes
row.names(to_print) <- colnames(accuracy)[-1] # Remise des noms des lignes
datatable(to_print,
filter = "top", # Filtrage au-dessus de la table
class = "cell-border stripe", # CSS
extensions = c("ColReorder",
"RowReorder"), # Reordonner manuellement à la main
options = list(pageLength = 12, # Page affichant 12 lignes
order = list(list(4, "desc")), # Ordonner par défaut par l'exactitude moyenne
colReorder = TRUE, # Plugin
rowReorder = TRUE)) %>% # Plugin
formatStyle(c("1 contre 1", "1 contre 2", "2 contre 1"),
background = styleColorBar(c(0, 1), 'lightgreen'), # Couleur vert clair pour les métriques par fold
backgroundSize = '100% 90%',
backgroundRepeat = 'no-repeat',
backgroundPosition = 'center') %>%
formatStyle("Moyenne",
background = styleColorBar(c(0, 1), 'pink'), # Couleur rose pour la métrique de moyenne
backgroundSize = '100% 90%',
backgroundRepeat = 'no-repeat',
backgroundPosition = 'center') %>%
formatPercentage(columns = c("1 contre 1", "1 contre 2", "2 contre 1"),
digits = 8) %>%
formatPercentage(columns = "Moyenne",
digits = 8)
# Affichage des résultats dans un tableau statique
formattable(accuracy[, c(1, 2:5)], list(formattable::area(col = xgb_LinearModel:xgb_GradientBoosting) ~ color_bar("orange")))
formattable(accuracy[, c(1, 6:9)], list(formattable::area(col = h2o_LinearModel:h2o_GradientBoosting) ~ color_bar("cyan")))
formattable(accuracy[, c(1, 10:13)], list(formattable::area(col = h2o_NN_32x6_ReLU:h2o_NN_16x16x6_Soft) ~ color_bar("yellow")))
Analyse du modèle de régression logistique
Lorsqu’on utilise les meilleurs paramètres pour le modèle de régression logistique, on obtient les résultats ci-dessous.
La salle 1 semble toujours causer des problèmes, il est possible qu’il y a toujours un mauvais conditionnement des données issues de cette salle. La salle 2 et 3 semblent les plus stables, et semble informer que :
- L’utilisation des 4 ancres permet d’obtenir le plus d’information, ce qui a été empiriquement vérifié par Bacciu et al. dans An experimental characterization of reservoir computing in ambient assisted living applications, et vérifié également ici (présence de toutes les ancres dans les features sélectionnées)
- Les features peuvent être en contradiction selon les salles (par exemple, Resi1_1 semble inutile pour le label 3 de la salle 2 avec un coefficient quasiment à 0, mais intégralement utile pour le même label pour la salle 3)
- L’entrainement de la salle 1 est si rapide que l’overfitting apparait immédiatement si le nombre d’itérations augmente à un nombre supérieur à 1 chiffre.
Il est clair également que la chemin 3 a été entièrement perdue en faveur de toutes les autres salles. Cela est une bonne et une mauvaise chose, car le modèle semble privilégier la prédiction de la chemin 1 en tout point (35% des prédictions pour uniquement 25% des valeurs). Il est tout à fait plausible que le chemin 3 soit difficile à prédire et que l’optimisation a privilégié les autres chemins, puisqu’ils pourraient être plus facile à optimiser linéairement.
# Compteur de temps
CurrentTime <- timer() # Chunk Préparation de l'analyse du dernier modèle de régression logistique
# Pré-initialisation des variables
predictedValues <- matrix(nrow = 314, ncol = 6)
evolution <- list()
temp_dt <- list()
temp_means <- data.frame(Feature = c(paste0(rep(c(paste0("Coef", 1:4), paste0("Rési", 1:4)), 4), paste0("_", inverse.rle(list(lengths = rep(8, 4), values = 1:4)))), paste0("PosInitiale_", 1:4))[which(best_weights$optimizer$discrete == 1)],
Fold_1 = numeric(sum(best_weights$optimizer$discrete == 1)),
Fold_2 = numeric(sum(best_weights$optimizer$discrete == 1)),
Fold_3 = numeric(sum(best_weights$optimizer$discrete == 1)),
Fold_Mean = numeric(sum(best_weights$optimizer$discrete == 1)),
Feature_Mean = numeric(sum(best_weights$optimizer$discrete == 1)),
Feature_SD = numeric(sum(best_weights$optimizer$discrete == 1)))
# Boucle d'entrainement 2 contre 1
for (i in 10:12) {
# Entrainement d'un modèle linéaire
temp_model <- xgb.train(data = training_xgb[[i]],
watchlist = list(test = testing_xgb[[i]]), # Données de validation
num_class = 6, # 6 classe de classification
nthread = 1, # 1 thread pour la reproduction des résultats
nrounds = 1000, # 1000 itérations, où arrêt prématuré
alpha = best_weights$optimizer$continuous[1], # Régularisation L1 (Lasso)
lambda = best_weights$optimizer$continuous[2], # Régularisation L2 (Ridge)
lambda_bias = best_weights$optimizer$continuous[3], # Régularisation L2 du biais (Ridge)
eta = 0.10, # Shrinkage pour le boosting
booster = "gblinear", # Type d'entrainement : linéaire ou non-linéaire
objective = "multi:softprob", # Gradient/Hessian pour l'optimisation par Gradient Descent
eval_metric = "merror", # Inexactitude de la classification (cette métrique est optimisée par xgboost)
maximize = FALSE, # Minimisation de l'erreur
early_stopping_rounds = 50, # Arrêt après 50 itérations sans amélioration de la métrique
verbose = FALSE, # Sans print des itérations
callbacks = list(cb.evaluation.log())) # Logging des données d'entrainement pour pouvoir récupérer les métriques)
# Enregistrement du log
evolution[[i - 9]] <- cbind(temp_model$evaluation_log, Fold = rep(13 - i, temp_model$niter))
# Entrainement du meilleur modèle (obtention des meilleurs coefficients)
temp_model <- xgb.train(data = training_xgb[[i]],
watchlist = list(test = testing_xgb[[i]]), # Données de validation
num_class = 6, # 6 classe de classification
nthread = 1, # 1 thread pour la reproduction des résultats
nrounds = temp_model$best_iteration, # Meilleure itération
alpha = best_weights$optimizer$continuous[1], # Régularisation L1 (Lasso)
lambda = best_weights$optimizer$continuous[2], # Régularisation L2 (Ridge)
lambda_bias = best_weights$optimizer$continuous[3], # Régularisation L2 du biais (Ridge)
eta = 0.10, # Shrinkage pour le boosting
booster = "gblinear", # Type d'entrainement : linéaire ou non-linéaire
objective = "multi:softprob", # Gradient/Hessian pour l'optimisation par Gradient Descent
eval_metric = "merror", # Inexactitude de la classification (cette métrique est optimisée par xgboost)
maximize = FALSE, # Minimisation de l'erreur
early_stopping_rounds = 99999, # Sans arrêt
verbose = FALSE, # Sans print des itérations
callbacks = list(cb.evaluation.log())) # Logging des données d'entrainement pour pouvoir récupérer les métriques)
# Prédiction du modèle linéaire
predictedValues[folds_test[[i]], ] <- t(matrix(predict(temp_model, testing_xgb[[i]], ntreelimit = 0), nrow = 6))
# Calcul et formattage de l'importance des variables
temp_importance <- data.table(Feature = temp_means[["Feature"]],
matrix(xgb.importance(model = temp_model)$Weight, ncol = 6))
colnames(temp_importance) <- c("Feature", paste0("Label_", 1:6))
temp_importance[["Sign"]] <- paste0(ifelse(temp_importance[[2]] >= 0, "+", "-"), ifelse(temp_importance[[3]] >= 0, "+", "-"), ifelse(temp_importance[[4]] >= 0, "+", "-"), ifelse(temp_importance[[5]] >= 0, "+", "-"), ifelse(temp_importance[[6]] >= 0, "+", "-"), ifelse(temp_importance[[7]] >= 0, "+", "-"))
temp_importance[, 2:7] <- abs(temp_importance[, 2:7, with = FALSE])
temp_means[[14 - i]] <- rowMeans(temp_importance[, 2:7, with = FALSE])
temp_importance[[paste0("Fold_", 13 - i, "_Mean")]] <- temp_means[[14 - i]]
# Enregistrement sous forme de tableau interactif
temp_dt[[i - 9]] <- datatable(temp_importance,
filter = "top", # Filtrage au-dessus de la table
class = "cell-border stripe", # CSS
extensions = c("ColReorder",
"RowReorder"), # Reordonner manuellement à la main
options = list(pageLength = 13, # Page affichant 13 lignes
order = list(list(9, "desc")), # Ordonner par défaut par les facteurs ayant le poids le plus gros
colReorder = TRUE, # Plugin
rowReorder = TRUE)) %>% # Plugin
formatStyle(paste0("Label_", 1:6),
background = styleColorBar(range(temp_importance[, 2:7, with = FALSE]), 'lightblue'), # Couleur bleue pour le coefficient
backgroundSize = '100% 90%',
backgroundRepeat = 'no-repeat',
backgroundPosition = 'center') %>%
formatStyle(paste0("Fold_", 13 - i, "_Mean"),
background = styleColorBar(range(temp_importance[[9]]), 'pink'), # Couleur rose pour la métrique de moyenne
backgroundSize = '100% 90%',
backgroundRepeat = 'no-repeat',
backgroundPosition = 'center') %>%
formatRound(columns = c(paste0("Label_", 1:6), paste0("Fold_", 13 - i, "_Mean")),
digits = 6)
}
# Calcul du poids moyen affecté à chaque feature
temp_means[[5]] <- rowMeans(temp_means[, 2:4]) # Poids moyen
temp_means[[6]] <- apply(mini_lm[, which(best_weights$optimizer$discrete == 1)], 2, function(x) {mean(x)}) # Moyenne de la feature dans les données
temp_means[[7]] <- apply(mini_lm[, which(best_weights$optimizer$discrete == 1)], 2, function(x) {sd(x)}) # Ecart-type de la feature dans les données
# Prépration du tableau interactif sur les poids moyens agrégés
temp_dt[[4]] <- datatable(temp_means,
filter = "top", # Filtrage au-dessus de la table
class = "cell-border stripe", # CSS
extensions = c("ColReorder",
"RowReorder"), # Reordonner manuellement à la main
options = list(pageLength = 13, # Page affichant 13 lignes
order = list(list(5, "desc")), # Ordonner par défaut par les facteurs ayant le poids le plus gros en moyenne
colReorder = TRUE, # Plugin
rowReorder = TRUE)) %>% # Plugin
formatStyle(paste0("Fold_", 1:3),
background = styleColorBar(range(temp_means[, 2:4]), 'lightblue'), # Couleur bleue pour le coefficient
backgroundSize = '100% 90%',
backgroundRepeat = 'no-repeat',
backgroundPosition = 'center') %>%
formatStyle("Fold_Mean",
background = styleColorBar(range(temp_means[[5]]), 'pink'), # Couleur rose pour la métrique de moyenne
backgroundSize = '100% 90%',
backgroundRepeat = 'no-repeat',
backgroundPosition = 'center') %>%
formatStyle("Feature_Mean",
background = styleColorBar(range(temp_means[[6]]), 'lightgreen'), # Couleur verte pour la moyenne des features
backgroundSize = '100% 90%',
backgroundRepeat = 'no-repeat',
backgroundPosition = 'center') %>%
formatStyle("Feature_SD",
background = styleColorBar(range(temp_means[[7]]), 'orange'), # Couleur verte pour l'écart-type des features
backgroundSize = '100% 90%',
backgroundRepeat = 'no-repeat',
backgroundPosition = 'center') %>%
formatRound(columns = c(paste0("Fold_", 1:3), "Fold_Mean", "Feature_Mean", "Feature_SD"),
digits = 6)
# Dépivotage du log
evolution <- rbindlist(evolution)
colnames(evolution) <- c("Iteration", "Exactitude", "Fold")
evolution$Exactitude <- 1 - evolution$Exactitude
evolution$Fold <- as.factor(evolution$Fold)
# Prédiction à partir des probabilités
predictedLabel <- data.frame(Label = group_path$path_ID, Prediction = apply(predictedValues, 1, function(x) {which.max(x)}))
timing(CurrentTime, "Préparation de l'analyse du dernier modèle de régression logistique")
Temps d'exécution de la tâche 'Préparation de l'analyse du dernier modèle de régression logistique' : 7.61 secondes.
# Affichage de l'évolution de la performance du modèle selon le nombre d'itération, sous forme de plot interactif
ggplotly(ggplot(data = evolution, aes_string(x = "Iteration", y = "Exactitude", group = "Fold", color = "Fold")) + geom_line() + geom_point() + scale_color_brewer(palette = "Set2") + theme_bw() + labs(title = "Evolution de l'exactitude par rapport au nombre d'itérations d'entrainement"), width = 960, height = 720)
# Affichage de la matrice de confusion sous forme de plot interactif
confusion_mat <- expand.grid(Label = 1:6, Prediction = 1:6)
confusion_mat <- merge(confusion_mat, data.table(predictedLabel)[, list(Freq = sum(.N)), by = list(Label, Prediction)], by = c("Label", "Prediction"), all.x = TRUE)
confusion_mat[["Freq"]][is.na(confusion_mat[["Freq"]])] <- 0
confusion_mat[["Label"]] <- as.factor(confusion_mat[["Label"]])
confusion_mat[["Prediction"]] <- as.factor(confusion_mat[["Prediction"]])
ggplotly(ggplot() + geom_rect(data = data.frame(cent = 1:6), size = 2, fill = NA, colour = "black", aes(xmin = cent - 0.5, xmax = cent + 0.5, ymin = cent - 0.5, ymax = cent + 0.5)) + geom_tile(data = confusion_mat, aes_string(x = "Label", y = "Prediction", fill = "Freq")) + geom_text(data = confusion_mat, aes_string(x = "Label", y = "Prediction", label = "Freq")) + scale_x_discrete(name = "Trajectoire Réelle") + scale_y_discrete(name = "Trajectoire Prédite") + scale_fill_gradientn(colours = rev(brewer.pal_extended(3, "PiYG"))) + labs(title = "Matrice de Confusion de la Trajectoire", fill = "Fréquence"), width = 960, height = 720)
# Affichage des tables à la fin car le formattage possède un bug inhérent lorsqu'on a plusieurs datatables (DT) dans le même chunk
# htmltools::tagList(temp_dt[[3]], temp_dt[[2]], temp_dt[[1]], temp_dt[[4]])
Conclusion
Nous avons fini le début de tache d’analyse après 6 heures de travail. Ce n’est qu’un début d’exploration, et les performances peuvent être bien meilleures par la création de features différentes. Nous aurions pu par exemple forcer diverses interactions entre les variables par des multiplications, ou encore créé des features d’une autre manière (exemples : prédiction des prochains points par série temporelle, utilisation uniquement d’un nombre restreint de points pour le calcul des features, utilisation des X derniers points comme features…).
Comme prévu durant la seconde analyse exploratoire avec la démonstration du problème de domaines de définition des variables (problème de linéarité), les modèles linéaires s’en sortent les meilleurs à la fin. Les modèles non-linéaires sont à la traine en performance, vu que :
- Pour tout problème de modélisation, il est plus facile d’apprendre un problème linéaire qu’un problème non-linéaire
- Pour tout problème de modélisation, si le problème se rapproche d’un problème linéaire, alors la performance d’un modèle linéaire sera supérieure à la performance d’un modèle non-linéaire dans la plupart des cas
De fait, l’important est de retenir que la performance du modèle dépend dans l’ordre de priorité :
- La plus importante : les features
- Peu important : les hyperparamètres
Voici le sommaire des résultats lorsqu’on a entrainé avec deux datasets contre une dataset :
# Chargement des scores enregistrés avec nettoyage
accuracy1 <- t(fread("scores/1_models.csv")[15, -1][1, c(1, 5, 2, 6, 3, 7, 4, 8, 9:12)])
accuracy2 <- t(fread("scores/2_models.csv")[15, -1][1, c(1, 5, 2, 6, 3, 7, 4, 8, 9:12)])
accuracy3 <- t(fread("scores/3_models.csv")[15, -1][1, c(1, 5, 2, 6, 3, 7, 4, 8, 9:12)])
accuracy4 <- t(fread("scores/4_models.csv")[15, -1][1, c(1, 5, 2, 6, 3, 7, 4, 8, 9:12)])
# Agrégation des scores
accuracy_agg <- data.table(Defaut = c(accuracy1[1:2], mean(accuracy1[9:10]), mean(accuracy1[11:12]), accuracy1[3:8]),
Approximation = c(accuracy2[1:2], mean(accuracy2[9:10]), mean(accuracy2[11:12]), accuracy2[3:8]),
Inversement = c(accuracy3[1:2], mean(accuracy3[9:10]), mean(accuracy3[11:12]), accuracy3[3:8]),
Selection = c(accuracy4[1:2], mean(accuracy4[9:10]), mean(accuracy4[11:12]), accuracy4[3:8]))
accuracy_agg[["Evolution"]] <- apply(accuracy_agg, 1, function(x) {max(x) - min(x)})
row.names(accuracy_agg) <- c("Régression logistique (xgb)",
"Régression logistique (h2o)",
"Réseau de neurones 32x6 (h2o)",
"Réseau de neurones 16x16x6 (h2o)",
"Arbre de décision (xgb)",
"Arbre de décision (h2o)",
"Forêt aléatoire (xgb)",
"Forêt aléatoire (h2o)",
"Arbres boostés (xgb)",
"Arbres boostés (h2o)")
# Affichage des scores dans un tableau interactif
datatable(accuracy_agg,
filter = "top", # Filtrage au-dessus de la table
class = "cell-border stripe", # CSS
extensions = c("ColReorder",
"RowReorder"), # Reordonner manuellement à la main
options = list(pageLength = 10, # Page affichant 10 lignes
colReorder = TRUE, # Plugin
rowReorder = TRUE)) %>% # Plugin
formatStyle(c("Defaut", "Approximation", "Inversement", "Selection"),
background = styleColorBar(c(0, max(accuracy_agg[["Selection"]])), 'lightgreen'), # Couleur vert clair pour les métriques par fold
backgroundSize = '100% 90%',
backgroundRepeat = 'no-repeat',
backgroundPosition = 'center') %>%
formatStyle(c("Evolution"),
background = styleColorBar(c(0, max(accuracy_agg[["Evolution"]])), 'pink'), # Couleur rose pour l'évolution de la métrique
backgroundSize = '100% 90%',
backgroundRepeat = 'no-repeat',
backgroundPosition = 'center') %>%
formatPercentage(columns = c("Defaut", "Approximation", "Inversement", "Selection", "Evolution"),
digits = 4)
En effet, on ne peut pas faire apprendre à un algorithme comment créer une feature qui n’existe pas en tant qu’entrée.
Il est tout à fait possible qu’un réseau de neurones avec une architecture plus avancée puisse faire beaucoup mieux, uniquement en utilisant les features initiales. C’est ce que Bacciu et al. ont réalisé dans An experimental characterization of reservoir computing in ambient assisted living applications spécifiquement pour prédire le changement de zone dans une salle (jusqu’à 99% d’exactitude).
LS0tDQp0aXRsZTogIkluZG9vciBVc2VyIE1vdmVtZW50IFByZWRpY3Rpb24gZnJvbSBSU1MgZGF0YSBEYXRhIFNldCINCm91dHB1dDoNCiAgcHJldHR5ZG9jOjpodG1sX3ByZXR0eToNCiAgICB0aGVtZTogYXJjaGl0ZWN0DQogICAgaGlnaGxpZ2h0OiBnaXRodWINCiAgICBjc3M6IHJlc2l6ZS5jc3MNCiAgICBudW1iZXJfc2VjdGlvbnM6IHllcw0KICAgIHNtb290aF9zY3JvbGw6IG5vDQogICAgdG9jOiB5ZXMNCiAgICB0b2NfZGVwdGg6IDYNCiAgaHRtbF9ub3RlYm9vazoNCiAgICBjb2xsYXBzZWQ6IG5vDQogICAgbnVtYmVyX3NlY3Rpb25zOiB5ZXMNCiAgICBzbW9vdGhfc2Nyb2xsOiBubw0KICAgIHRoZW1lOiByZWFkYWJsZQ0KICAgIHRvYzogeWVzDQogICAgdG9jX2RlcHRoOiA2DQogICAgdG9jX2Zsb2F0OiB5ZXMNCiAgaHRtbF9kb2N1bWVudDoNCiAgICBjb2xsYXBzZWQ6IG5vDQogICAgY3NzOiByZXNpemUuY3NzDQogICAgbnVtYmVyX3NlY3Rpb25zOiB5ZXMNCiAgICBzbW9vdGhfc2Nyb2xsOiBubw0KICAgIHRoZW1lOiByZWFkYWJsZQ0KICAgIHRvYzogeWVzDQogICAgdG9jX2RlcHRoOiA2DQogICAgdG9jX2Zsb2F0OiB5ZXMNCmRhdGU6ICJgciBmb3JtYXQoU3lzLnRpbWUoKSwgJyVhICViICVkICVZICVYJylgIg0KLS0tDQoNCiMgSW50cm9kdWN0aW9uDQoNCkNlY2kgZXN0IGwnYW5hbHlzZSBwb3VyIGxhIHTDomNoZSBkJ2FuYWx5c2UgZGUgY2xhc3NpZmljYXRpb24gbXVsdGktY2xhc3NlIHN1ciBsYSBkYXRhc2V0ICoiSW5kb29yIFVzZXIgTW92ZW1lbnQgUHJlZGljdGlvbiBmcm9tIFJTUyBkYXRhIERhdGEgU2V0IiouDQoNCkwnb2JqZWN0aWYgZXN0IGRlIHByw6lkaXJlIGxlIHBhdHRlcm4gZGVzIG1vdXZlbWVudHMgZGVzIHV0aWxpc2F0ZXVycyBkYW5zIHVuIGVudmlyb25tbWVudCBkZSB0cmF2YWlsIMOgIHRyYXZlcnMgZGVzIHPDqXJpZXMgdGVtcG9yZWxsZXMgZ8OpbsOpcsOpZXMgcGFyIHVuIHLDqXNlYXUgZGUgY2FwdGV1cnMgc2Fucy1maWxzLiBMZXMgZG9ubsOpZXMgY29udGllbm5lbnQgZGVzIHN0cmVhbXMgZGUgZG9ubsOpZXMgdGVtcG9yZWxsZXMsIGNvbnNpc3RhbnQgZW4gbGEgZm9yY2UgZGVzIHNpZ25hdXggcmFkaW9zIG1lc3Vyw6lzIGVudHJlIGxlcyBub2V1ZHMgZHUgcsOpc2VhdSBkZSBjYXB0ZXVycyBzYW5zLWZpbHMsIMOgIHVuZSBmcsOpcXVlbmNlIGRlIDggSHogcmVtYXBww6llcyBkYW5zIGwnaW50ZXJ2YWxsZSBbLTEsIDFdLg0KDQpDZSByw6lzZWF1IHNlIGNvbXBvc2UgZGUgNSBjYXB0ZXVyczoNCg0KKiA0IGFuY3LDqWVzIGRhbnMgbCdlbnZpcm9ubmVtZW50DQoqIDEgYW5jcsOpZSBzdXIgbCd1dGlsaXNhdGV1cg0KDQpMYSB0YWNoZSBuw6ljZXNzaXRlIGRlIHByw6lkaXJlIGxhIGNsYXNzZSBpbmRpcXVhbnQgbGEgdHJhamVjdG9pcmUgZCd1biB1dGlsaXNhdGV1ciwgcGFybWkgNiBtb3V2ZW1lbnRzIHNww6ljaWZpcXVlcy4NCg0KT24gZGlzcG9zZSBkZSAzMTQgc8OpcXVlbmNlcywgcG91ciB1biBub21icmUgdG90YWwgZGUgMTMxOTcgw6l0YXBlcy4gRGUgZmFpdCwgbGVzIHPDqXJpZXMgbidvbnQgcGFzIGZvcmPDqW1lbnQgbGEgbcOqbWUgZHVyw6llLg0KDQpBIG5vdGVyIDogbGEgcHJlbWnDqHJlIGRhdGFzZXQgbmUgcG9zc8OoZGUgcGFzIGxhIGNsYXNzZSAzIGR1ZSDDoCBkZXMgY29udHJhaW50ZXMgcGh5c2lxdWVzLiBFbiBjb25zw6lxdWVuY2UsIGlsIHNlcmEgaW50w6lyZXNzZW50IGRlIHbDqXJpZmllciB0b3V0ZSBkaWZmw6lyZW5jZSBwb3RlbnRpZWxsZSBlbnRyZSBsZXMgcHLDqWRpY3Rpb25zIHNlbG9uIGxlcyBkYXRhc2V0cyB1dGlsaXPDqWVzIHBvdXIgbCdlbnRyYWluZW1lbnQvdmFsaWRhdGlvbi4NCg0KTm90ZTogbGUgQ1NTIGEgw6l0w6kgbW9kaWZpw6kgbWFudWVsbGVtZW50LiBMYSBwcmVtacOocmUgb2NjdXJyZW5jZSBkZSAiODQwIiAob3UgIjEyMDAiIHBvdXIgbGUgbm90ZWJvb2spIGVzdCByZW1wbGFjw6llIHBhciAxMjQwIChvdSAiMTYwMCIgcG91ciBsZSBub3RlYm9rKSBwb3VyIG1pZXV4IGNvdXZyaXIgbCdlc3BhY2UgaG9yaXpvbnRhbC4gVm91cyBwb3V2ZXogbW9kaWZpZXIgbGUgQ1NTIG1hbnVlbGxlbWVudCBkYW5zIGxlIGNhcyBvw7kgdm90cmUgZMOpZmluaXRpb24gZCfDqWNyYW4gZXN0IHRyb3AgZmFpYmxlIHBvdXIgMTI0MCAob3UgMTYwMCkgcGl4ZWxzLg0KDQpMJ2ludMOpZ3JhbGl0w6kgZGVzIG1vZMOobGVzIGV0IGRhdGFzZXRzIGludGVybcOpZGlhaXJlcyBzb250IGV4cG9ydMOpZXMgcGFyIGxlIHNjcmlwdC4NCg0KIyMgUHLDqXBhcmF0aW9uDQoNCkF2YW50IGRlIGNvbW1lbmNlciB0b3V0ZSByZWNoZXJjaGUsIG5vdXMgZGV2b25zIGNoYXJnZXIgbGVzIGRvbm7DqWVzIGVuIG3DqW1vaXJlLiBEZSBwbHVzLCBub3VzIGRldm9ucyBtZXR0cmUgbCdlbmNvZGluZyBkZSBSIGVuIFVURi04LCBwdWlzcXUnb24gdXRpbGlzZSBkZXMgY2FyYWN0w6hyZXMgaW50ZXJkaXRzIGF1IGZvcm1hdCBJU08tODg1OS0xLiBDZWxhIHNlIGZhaXQgdG91dCBzaW1wbGVtZW50IGRhbnMgUlN0dWRpbyB2aWEgVG9vbHMgPiBHbG9iYWwgT3B0aW9ucyA+IENvZGUgPiBTYXZpbmcgPiBEZWZhdWx0IFRleHQgRW5jb2RpbmcuDQoNCiMjIyBDaGFyZ2VtZW50IGRlcyBsaWJyYXJpZXMNCg0KTGVzIGluZm9ybWF0aW9ucyBjaS1kZXNzb3VzIG5vdXMgcGVybWV0dHJvbnQgZGUgcmUtY3LDqWVyIGwnZW52aXJvbm5lbWVudCBkYW5zIGxlcXVlbCBvbiB0cmF2YWlsbGUuIFBvdXIgaW5mb3JtYXRpb24sIGxhIGNvbmZpZ3VyYXRpb24gaGFyZHdhcmUgZXN0IGxhIHN1aXZhbnRlIDoNCg0KLSBQcm9jZXNzZXVyIDogSW50ZWwgaTctMzkzMEsgKDEyIHNvY2tldHMgdmlydHVhbGlzw6lzKQ0KLSBSQU0gOiA1NCBHQiAoMTUwIEdCIHN3YXApDQotIENhcnRlIGdyYXBoaXF1ZSA6IGF1Y3VuZQ0KDQpEZSBtw6ptZSwgYXUgbml2ZWF1IHNvZnR3YXJlIDoNCg0KLSBTeXN0w6htZSBkJ2V4cGxvaXRhdGlvbiA6IFdpbmRvd3MgU2VydmVyIDIwMTIgUjINCi0gUjogTWljcm9zb2Z0IFIgQ2xpZW50LCB2ZXJzaW9uIDMuMy4yIGRlIFIgKyBJbnRlbCBNS0wNCi0gUlN0dWRpbyBQcmV2aWV3LCB2ZXJzaW9uIHN1cMOpcmlldXJlIG91IMOpZ2FsZSDDoCAxLjAuMTM2IChyw6lzb2x1dGlvbiBkdSBwcm9ibMOobWUgZCdlbmNvZGluZyBkZXMgY2FyYWN0w6hyZXMgZW4gTWFya2Rvd24pDQotIFJ0b29scyBwb3VyIFIgMy4zLnggcG91ciBsYSBjb21waWxhdGlvbiBkZSBwYWNrYWdlcyBwb3VyIFINCi0gTWluR1cgNi4yLjAgcG91ciBsYSBjb21waWxhdGlvbiBkZSBjb2RlIEMrKw0KLSBKYXZhIDggVXBkYXRlIDExMSBwb3VyIGwndXRpbGlzYXRpb24gZGUgSDJPDQotIEdpdCBCYXNoIHBvdXIgbCd1dGlsaXNhdGlvbiBkZSBCYXNoIChwb3VyIE1pbkdXKQ0KDQpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0NCiMgTm90ZWJvb2sNCmxpYnJhcnkoa25pdHIpDQpvcHRzX2tuaXQkc2V0KHJvb3QuZGlyID0gIkU6L0F1eGl2aWEvIikgIyBXb3JraW5nIGRpcmVjdG9yeQ0KI29wdHNfY2h1bmskc2V0KHRpZHkgPSBGQUxTRSkgIyBDb2RlIGxpc2libGUgZXQgYcOpcsOpDQpvcHRzX2NodW5rJHNldChyZXN1bHRzID0gImhvbGQiKSAjIE91dHB1dHMgZMOpZsOpcsOpcyBlbiBmaW4gZGUgY2hhcXVlIGNodW5rIGRlIGNvZGUNCiNvcHRpb25zKGZvcm1hdFIuYmxhbmsgPSBUUlVFKSAjIENvbnNlcnZhdGlvbiBkZXMgbGlnbmVzIHZpZGVzIHBvdXIgbGEgbGlzaWJpbGl0w6kNCm9wdHNfY2h1bmskc2V0KGZpZy5hbGlnbiA9ICJjZW50ZXIiKSAjIEFsaWduZW1lbnQgaG9yaXpvbnRhbCBkZXMgZmlndXJlcw0KYGBgDQoNCmBgYHtyIExpYnJhaXJpZXMsIHJlc3VsdHM9ImhpZGUiLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KIyBDaGFyZ2VtZW50IGRlIGRvbm7DqWVzDQpsaWJyYXJ5KGRhdGEudGFibGUpDQoNCiMgUsOpZ3Jlc3Npb24gbGluw6lhaXJlIGVuIEMrKw0KbGlicmFyeShSY3BwQXJtYWRpbGxvKQ0KDQojIGh0bWx3aWRnZXRzIC8gM2RqcyAvIFBsb3RseQ0KbGlicmFyeShwbG90bHkpDQoNCiMgaHRtbHdpZGdldHMgLyBkYXRhdGFibGVzIC8gZm9ybWF0dGFibGUNCmxpYnJhcnkoRFQpDQpsaWJyYXJ5KGZvcm1hdHRhYmxlKQ0KDQojIEdyYXBoaXF1ZSB0YWJsZXBsb3QNCmxpYnJhcnkodGFicGxvdCkNCg0KIyBNYWNoaW5lIExlYXJuaW5nIGfDqW7DqXJhbGlzdGUgZW4gQysrDQpsaWJyYXJ5KHhnYm9vc3QpDQoNCiMgTWFjaGluZSBMZWFybmluZyBnw6luw6lyYWxpc3RlIGVuIEphdmENCmxpYnJhcnkoaDJvKQ0KbG9jYWxIMk8gPSBoMm8uaW5pdChudGhyZWFkcyA9IDEsIG1heF9tZW1fc2l6ZSA9ICI0RyIpICMgMSBUaHJlYWQgKHJlcHJvZHVjdGlvbiBkZXMgcsOpc3VsdGF0cyksIDRHQiBkZSBtw6ltb2lyZSB2aXZlDQpoMm8ubm9fcHJvZ3Jlc3MoKSAjIENhY2hlIGxhIGJhcnJlIGRlIHByb2dyZXNzaW9uDQoNCiMgT3B0aW1pc2V1ciBjb250aW51IGV0IGRpc2NyZXQNCmxpYnJhcnkoQ0VvcHRpbSkNCg0KIyBUaW1pbmcNCmxpYnJhcnkoUi51dGlscykNCg0KIyBQbGVpbiBkJ3V0aWxpdGFpcmVzDQpsaWJyYXJ5KExhdXJhZSkNCg0KIyBGb25jdGlvbiBkZSB0aW1pbmcgOiBwcmVuZCB1biB0ZW1wcyBwYXNzw6kgZXQgdW5lIHTDomNoZSwgYWZmaWNoZSBsYSBkaWZmw6lyZW5jZSBkZSB0ZW1wcyBhdSBjZW50acOobWUgZGUgc2Vjb25kZQ0KdGltaW5nIDwtIGZ1bmN0aW9uKHRpbWVyLCB3aGF0ID0gIj8iKSB7DQogIGNhdCgiVGVtcHMgZCdleMOpY3V0aW9uIGRlIGxhIHTDomNoZSAnIiwgd2hhdCwgIicgOiAiLCBzcHJpbnRmKCIlLjAyZiIsICgoU3lzdGVtJGN1cnJlbnRUaW1lTWlsbGlzKCkgLSB0aW1lcikgLyAxMDAwKSksICIgc2Vjb25kZXMuICBcbiIsIHNlcCA9ICIiKSAjIERvdWJsZSBlc3BhY2UgcG91ciDDqXZpdGVyIGxlIGJ1ZyBkJ2VzcGFjZSBlbiBNYXJrZG93biBSDQp9DQoNCiMgSW5mb3JtYXRpb25zIHN1ciBsYSBzZXNzaW9uDQpzZXNzaW9uSW5mbygpDQpgYGANCg0KIyMjIENoYXJnZW1lbnQgZGVzIGRvbm7DqWVzDQoNClBvdXIgY2hhcmdlciBsZXMgZG9ubsOpZXMsIHF1aSBzZSB0cm91dmVudCBkZSBtYW5pw6hyZSBzw6lwYXLDqWUgZGFucyBkZXMgZmljaGllcnMsIG5vdXMgZGV2b25zIGxlcyBvdXZyaXIgdW4gcGFyIHVuLiBDZWxhIHNlIGZhaXQgZGUgbWFuacOocmUgdHJpdmlhbGUgc3VyIFIgcGFyIHVuZSBib3VjbGU6DQoNCi0gTm91cyBhdm9ucyAzMTQgZmljaGllcnMgbnVtw6lyb3TDqXMgZGUgMSDDoCAzMTQgKGV0IG5vbiBwYXMgMDAxIMOgIDMxNCkgc291cyBsYSBmb3JtZSBNb3ZlbWVudEFBTF9SU1NfWC5jc3YgKHJlbXBsYWNlciBYKSBkYW5zIGxlIGRvc3NpZXIgL2RhdGFzZXQsIGRvbnQgbGEgcHJlbWnDqHJlIGNvbG9ubmUgcG9zc8OoZGUgbGUgY2FyYWN0w6hyZSBkacOoc2UgZW4gdHJvcCAocG91ciBtYXJxdWVyIGxlcyBjb2xvbm5lcykgcXVpIGRvaXQgw6p0cmUgZW5sZXbDqWUNCi0gTm91cyBhdm9ucyBsZXMgbGFiZWxzIGRhbnMgbGUgZmljaGllciBub21tw6kgTW92ZW1lbnRBQUxfdGFyZ2V0LmNzdiBkYW5zIGxlIGRvc3NpZXIgL2RhdGFzZXQNCi0gTGVzIGRvbm7DqWVzIGFubmV4ZXMgZGUgZmFtaWxsZSBkZSBtb3V2ZW1lbnRzIChNb3ZlbWVudEFBTF9EYXRhc2V0R3JvdXAuY3N2KSBldCBkZSBjaGVtaW5zIChNb3ZlbWVudEFBTF9QYXRocy5jc3YpIHNvbnQgZGFucyBsZSBkb3NzaWVyIC9ncm91cA0KDQpgYGB7ciBDaGFyZ2VtZW50RGF0YX0NCiMgQ29tcHRldXIgZGUgdGVtcHMNCkN1cnJlbnRUaW1lIDwtIHRpbWVyKCkgIyBDaHVuayBDaGFyZ2VtZW50IGRlcyBkb25uw6llcyBlbiBtw6ltb2lyZQ0KDQojIFByw6ktaW5pdGlhbGlzYXRpb24gZGVzIHZhcmlhYmxlcw0KZGF0YV9wcmUgPC0gbGlzdCgpDQoNCiMgQ2hhcmdlbWVudCBkZXMgZG9ubsOpZXMNCmZvciAoaSBpbiAxOjMxNCkgew0KICBkYXRhX3ByZVtbaV1dIDwtIGZyZWFkKHBhc3RlMCgiZGF0YXNldC9Nb3ZlbWVudEFBTF9SU1NfIiwgaSwgIi5jc3YiKSwgc2VwID0gIiwiLCB2ZXJib3NlID0gRkFMU0UsIHNob3dQcm9ncmVzcyA9IEZBTFNFLCBjb2wubmFtZXMgPSBjKCJSU1NfYW5jaG9yMSIsICJSU1NfYW5jaG9yMiIsICJSU1NfYW5jaG9yMyIsICJSU1NfYW5jaG9yNCIpKQ0KfQ0KDQojIENoYXJnZW1lbnQgZGVzIGRvbm7DqWVzIGFubmV4ZXMNCmxhYmVscyA8LSBmcmVhZCgiZGF0YXNldC9Nb3ZlbWVudEFBTF90YXJnZXQuY3N2Iiwgc2VwID0gIiwiLCB2ZXJib3NlID0gRkFMU0UsIHNob3dQcm9ncmVzcyA9IEZBTFNFLCBjb2wubmFtZXMgPSBjKCJzZXF1ZW5jZV9JRCIsICJjbGFzc19sYWJlbCIpKQ0KbGFiZWxzJGNsYXNzX2xhYmVsW2xhYmVscyRjbGFzc19sYWJlbCA9PSAtMV0gPC0gMA0KZ3JvdXBfcm9vbSA8LSBmcmVhZCgiZ3JvdXBzL01vdmVtZW50QUFMX0RhdGFzZXRHcm91cC5jc3YiLCBzZXAgPSAiLCIsIHZlcmJvc2UgPSBGQUxTRSwgc2hvd1Byb2dyZXNzID0gRkFMU0UsIGNvbC5uYW1lcyA9IGMoInNlcXVlbmNlX0lEIiwgImRhdGFzZXRfSUQiKSkNCmdyb3VwX3BhdGggPC0gZnJlYWQoImdyb3Vwcy9Nb3ZlbWVudEFBTF9QYXRocy5jc3YiLCBzZXAgPSAiLCIsIHZlcmJvc2UgPSBGQUxTRSwgc2hvd1Byb2dyZXNzID0gRkFMU0UsIGNvbC5uYW1lcyA9IGMoInNlcXVlbmNlX0lEIiwgInBhdGhfSUQiKSkNCg0KIyBUZW1wcyBuw6ljZXNzYWlyZQ0KdGltaW5nKEN1cnJlbnRUaW1lLCAiQ2hhcmdlbWVudCBkZXMgZG9ubsOpZXMgZW4gbcOpbW9pcmUiKQ0KYGBgDQoNCiMjIFByZW1pw6hyZSBBbmFseXNlIEV4cGxvcmF0b2lyZQ0KDQojIyMgVGVudGF0aXZlIGRlIGNvbXByw6loZW5zaW9uIGR1IHByb2Jsw6htZSA6IHByb2Jsw6htZSBpbmjDqXJlbnQgYXV4IGRvbm7DqWVzDQoNClBvdXIgdGVudGVyIGRlIGNvbXByZW5kcmUgbGUgcHJvYmzDqG1lLCBvbiB2YSBwcmVuZHJlIGxlcyAxNiBkZXJuaWVycyBwb2ludHMgZGUgY2hhcXVlIHPDqXJpZSB0ZW1wb3JlbGxlLCBldCBsZXMgYWZmaWNoZXIgdmlhIDNkanMgdmlhIGwnaW50ZXJmYWNlIFBsb3RseS4NCg0KSWwgZXN0IGNsYWlyIGV0IG5ldCBxdWUgbGEgU2FsbGUgMSBkw6ltb250cmUgdW4gY29tcG9ydGVtZW50IGlycsOpZ3VsaWVyIHBhciByYXBwb3J0IGF1eCBkZXV4IGF1dHJlcyBzYWxsZXMuIFVuZSBhbmFseXNlIHRyYWplY3RvaXJlIHBhciB0cmFqZWN0b2lyZSBtb250cmUgcXVlIGxhIDoNCg0KKiBUcmFqZWN0b2lyZSAxIDogY2FuYWwgb3Bwb3PDqSBzdXIgbCdhbmNyZSAxLCAyLCAzLCBldCA0DQoqIFRyYWplY3RvaXJlIDIgOiBjYW5hbCBvcHBvc8OpIHN1ciBsJ2FuY3JlIDEsIDIsIDQsIGF2ZWMgdW4gZ2FpbiBmYWlibGUgc3VyIGwnYW5jcmUgMw0KKiBUcmFqZWN0b2lyZSAzIDogaW5leGlzdGFuY2UgKGNvbW1lIHByw6l2dSwgZCdhcHLDqHMgbGVzIMOpbm9uY8OpcyAtIHJlcHLDqXNlbnTDqSBwYXIgdW5lIGxpZ25lIGJsZXVlIGZpeMOpZSDDoCAwKQ0KKiBUcmFqZWN0b2lyZSA0IDogY2FuYWwgb3Bwb3PDqSBzdXIgbCdhbmNyZSAxLCAyLCA0LCBhdmVjIHVuIGdhaW4gZm9ydCBzdXIgbCdhbmNyZSAzDQoqIFRyYWplY3RvaXJlIDUgOiBjYW5hbCBvcHBvc8OpIHN1ciBsJ2FuY3JlIDEsIDIsIDMsIGV0IDQNCiogVHJhamVjdG9pcmUgNiA6IGNhbmFsIG9wcG9zw6kgc3VyIGwnYW5jcmUgMSwgMiwgMywgZXQgNA0KDQpJbCBmYXVkcmEgb2JsaWdhdG9pcmVtZW50IHLDqWFsaXNlciB1bmUgdHJhbnNmb3JtYXRpb24gc3VyIGxlcyBkb25uw6llcyBkZSBsYSBzYWxsZSAxIGFmaW4gcXVlIGxlcyBtb2TDqGxlcyBwdWlzc2VudCDDqnRyZSDDqXZhbHXDqXMgZGUgbWFuacOocmUgZmlhYmxlLiBJbCBlc3QgdG91dCDDoCBmYWl0IHBvc3NpYmxlIHF1ZSBsb3JzIGRlIGxhIGNvbGxlY3RlIGRlcyBkb25uw6llcywgbGVzIHNhbGxlcyAxIGV0IDMgb250IMOpdMOpIGludmVyc8OpZXMsIHF1ZSBkZXMgYW5jcmVzIG9udCDDqXTDqSBtYWwgcG9zaXRpb25uw6llcywgb3UgZW5jb3JlIHF1J2lsIHkgYSBldSB1bmUgZXJyZXVyIGRlIG1hbmlwdWxhdGlvbiBkZXMgZG9ubsOpZXMgcG91ciBjcsOpZXIgbGVzIGRhdGFzZXRzIHBhciBzYWxsZS4gVnUgbGUgcHJvYmzDqG1lLCBhdWN1bmUgZGVzIHRyb2lzIGh5cG90aMOoc2VzIG4nZXN0IHZhbGlkw6llIG5pIGV4Y2x1ZS4NCg0KRW4gcsOpYWxpdMOpLCBsJ2ltYWdlIHN1aXZhbnRlLCBkJ2FwcsOocyBsYSByZWNoZXJjaGUgKkFuIGV4cGVyaW1lbnRhbCBjaGFyYWN0ZXJpemF0aW9uIG9mIHJlc2Vydm9pciBjb21wdXRpbmcgaW4gYW1iaWVudCBhc3Npc3RlZCBsaXZpbmcgYXBwbGljYXRpb25zIChCYWNjaXUgZXQgYWwuLDIwMTIpKiBleHBsaXF1ZSBsYSByYWlzb24gZGUgbGEgZGlzcGFyaXTDqSBlbnRyZSBsYSBzYWxsZSAxIGV0IGxlcyBkZXV4IGF1dHJlcyBzYWxsZXMhDQoNCiFbUmVjaGVyY2hlIGRlIEJhY2NpdSBldCBhbC5dKGJhY2NpdTIwMTMuanBnKQ0KDQpJbCBzZXJhaXQgaW50w6lyZXNzYW50IGRlIHRyYXZhaWxsZXIgw6AgbGEgZm9pcyBzdXIgOg0KDQotIEwnaW52ZXJzaW9uIGRlcyBhbmNyZXMgQTEvQTMgZXQgQTIvQTQNCi0gTGEgdGVudGF0aXZlIGRlIHJlcHJvZHVjdGlvbiBkZXMgcsOpc3VsdGF0cyBkZXMgcXVhdHJlIGFuY3Jlcw0KDQpgYGB7ciBBZ3JlZ2F0aW9uUHJvYmxlbWV9DQojIENvbXB0ZXVyIGRlIHRlbXBzDQpDdXJyZW50VGltZSA8LSB0aW1lcigpICMgQ2h1bmsgQWdyw6lnYXRpb24gZGVzIGRvbm7DqWVzDQoNCiMgUHLDqS1pbml0aWFsaXNhdGlvbiBkZSBsYSBzw6lyaWUgdGVtcG9yZWxsZSBkw6lwaXZvdMOpZSBwYXIgbGUgbGFiZWwNCiMgDQojIEFyY2hpY3RlY3R1cmUgZGUgbGEgbWF0cmljZSAoZMOpcGl2b3TDqWUgc2FucyBsYSBzYWxsZSkgMzg0eDQgKDY0IG9ic2VydmF0aW9ucyBwYXIgbGFiZWwsIDE2IG9ic2VydmF0aW9ucyBwYXIgYW5jcmUgcGFyIGxhYmVsKSA6DQojIElEICBTdHJlbmd0aCAgICBBbmNob3IgICAgICBMYWJlbCAgICAgICBUaW1lDQojICAxICAgIHZhbF9hMSAgICAgICAgIDEgICAgICAgICAgMSAgICAgICAgICAxDQojICAyICAgIHZhbF9hMiAgICAgICAgIDEgICAgICAgICAgMSAgICAgICAgICAyDQojICAuLi4NCiMgMTYgICB2YWxfYTE2ICAgICAgICAgMSAgICAgICAgICAxICAgICAgICAgMTYNCiMgMTcgICB2YWxfYTE3ICAgICAgICAgMiAgICAgICAgICAxICAgICAgICAgIDENCiMgIC4uLg0KIyAzMiAgIHZhbF9hMzIgICAgICAgICAyICAgICAgICAgIDEgICAgICAgICAxNg0KIyAzMyAgIHZhbF9hMzMgICAgICAgICAzICAgICAgICAgIDEgICAgICAgICAgMQ0KIyAgLi4uDQojIDQ4ICAgdmFsX2E0OCAgICAgICAgIDMgICAgICAgICAgMSAgICAgICAgIDE2DQojIDQ5ICAgdmFsX2E0OSAgICAgICAgIDQgICAgICAgICAgMSAgICAgICAgICAxDQojICAuLi4NCiMgNjQgICB2YWxfYTE2ICAgICAgICAgNCAgICAgICAgICAxICAgICAgICAgMTYNCiMgNjUgICB2YWxfYTE3ICAgICAgICAgMSAgICAgICAgICAyICAgICAgICAgIDENCiMgIC4uLg0KIyBJRChhbmNob3IsIGxhYmVsLCB0aW1lKSA9ICgobGFiZWwgLSAxKSAqIDY0KSArICgoYW5jaG9yIC0gMSkgKiAxNikgKyB0aW1lDQphdmdfc2VyaWVzIDwtIGRhdGEudGFibGUobWF0cml4KHJlcCgwLCAxNiAqIDQgKiA2ICogNiksIG5yb3cgPSAxNiAqIDQgKiA2LCBuY29sID0gNCkpDQpjb2xuYW1lcyhhdmdfc2VyaWVzKSA8LSBjKCJTdHJlbmd0aCIsICJBbmNob3IiLCAiTGFiZWwiLCAiVGltZSIpDQoNCiMgUHLDqS1maWxsaW5nIGRlcyBmYWN0ZXVycyAoQW5jaG9yLCBMYWJlbCwgVGltZSkNCmF2Z19zZXJpZXNbWyJBbmNob3IiXV0gPC0gYXMuZmFjdG9yKHJlcChpbnZlcnNlLnJsZShsaXN0KGxlbmd0aHMgPSByZXAoMTYsIDQpLCB2YWx1ZXMgPSAxOjQpKSwgNikpDQpsZXZlbHMoYXZnX3Nlcmllc1tbIkFuY2hvciJdXSkgPC0gcGFzdGUoIkFuY3JlIiwgMTo0LCBzZXAgPSAiIikNCmF2Z19zZXJpZXNbWyJMYWJlbCJdXSA8LSBhcy5mYWN0b3IoaW52ZXJzZS5ybGUobGlzdChsZW5ndGhzID0gcmVwKDE2ICogNCwgNiksIHZhbHVlcyA9IDE6NikpKQ0KbGV2ZWxzKGF2Z19zZXJpZXNbWyJMYWJlbCJdXSkgPC0gcGFzdGUoIlRyYWplY3RvaXJlIiwgMTo2LCBzZXAgPSAiIikNCmF2Z19zZXJpZXNbWyJUaW1lIl1dIDwtIHJlcCgxOjE2LCA2ICogNCkNCg0KIyBUcmFuc2Zvcm1hdGlvbiBlbiBsaXN0ZSBwYXIgc2FsbGUgw6AgZMOpcGl2b3RlciBwYXIgbGEgc3VpdGUNCmF2Z19zZXJpZXMgPC0gbGlzdChjYmluZChhdmdfc2VyaWVzLCBSb29tID0gcmVwKDEsIDM4NCkpLA0KICAgICAgICAgICAgICAgICAgIGNiaW5kKGF2Z19zZXJpZXMsIFJvb20gPSByZXAoMiwgMzg0KSksDQogICAgICAgICAgICAgICAgICAgY2JpbmQoYXZnX3NlcmllcywgUm9vbSA9IHJlcCgzLCAzODQpKSkNCg0KIyBQcsOpLWNvbXB0ZSBkdSBub21icmUgZCdvY2N1cnJlbmNlIGRlcyBsYWJlbHMNCmxhYmVsX2NvdW50IDwtIGxpc3QodGFidWxhdGUoZ3JvdXBfcGF0aFtbInBhdGhfSUQiXV1bZ3JvdXBfcm9vbVtbImRhdGFzZXRfSUQiXV0gPT0gMV0pLA0KICAgICAgICAgICAgICAgICAgICB0YWJ1bGF0ZShncm91cF9wYXRoW1sicGF0aF9JRCJdXVtncm91cF9yb29tW1siZGF0YXNldF9JRCJdXSA9PSAyXSksDQogICAgICAgICAgICAgICAgICAgIHRhYnVsYXRlKGdyb3VwX3BhdGhbWyJwYXRoX0lEIl1dW2dyb3VwX3Jvb21bWyJkYXRhc2V0X0lEIl1dID09IDNdKSkNCg0KIyBEw6l0ZXJtaW5hdGlvbiBkZXMgMTYgZGVybmnDqHJlcyBvYnNlcnZhdGlvbnMgKDIgc2Vjb25kZXMgw6AgOCBIeiksIG1veWVubmlzw6llcw0KZm9yIChpIGluIDE6MzE0KSB7DQogIHRlbXBfbGFiZWwgPC0gKChncm91cF9wYXRoW1sicGF0aF9JRCJdXVtpXSAtIDEpICogNjQpICsgMSAjIExpZ25lIGRlIGTDqW1hcnJhZ2UgZGFucyBsYSBtYXRyaWNlIGFncsOpZ8OpZQ0KICB0ZW1wX29icyA8LSBucm93KGRhdGFfcHJlW1tpXV0pIC0gMTUgIyBMaWduZSBkZSBkw6ltYXJyYWdlIGRhbnMgbGEgbWF0cmljZSDDoCBzYXV2ZWdhcmRlcg0KICANCiAgYXZnX3Nlcmllc1tbZ3JvdXBfcm9vbVtbImRhdGFzZXRfSUQiXV1baV1dXVt0ZW1wX2xhYmVsOih0ZW1wX2xhYmVsICsgNjMpLCAxXSA8LSBhdmdfc2VyaWVzW1tncm91cF9yb29tW1siZGF0YXNldF9JRCJdXVtpXV1dW3RlbXBfbGFiZWw6KHRlbXBfbGFiZWwgKyA2MyksIDFdICsgKHVubGlzdChkYXRhX3ByZVtbaV1dW3RlbXBfb2JzOih0ZW1wX29icyArIDE1KSwgXSkgLyBsYWJlbF9jb3VudFtbZ3JvdXBfcm9vbVtbImRhdGFzZXRfSUQiXV1baV1dXVtncm91cF9wYXRoW1sicGF0aF9JRCJdXVtpXV0pDQp9DQoNCiMgRMOpcGl2b3RhZ2UgZGUgbGEgdmFyaWFibGUgZMOpZmluaXNzYW50IGxhIHNhbGxlDQphdmdfc2VyaWVzIDwtIHJiaW5kKGF2Z19zZXJpZXNbWzFdXSwgYXZnX3Nlcmllc1tbMl1dLCBhdmdfc2VyaWVzW1szXV0pDQphdmdfc2VyaWVzW1siUm9vbSJdXSA8LSBhcy5mYWN0b3IoaW52ZXJzZS5ybGUobGlzdChsZW5ndGhzID0gcmVwKDM4NCwgMyksIHZhbHVlcyA9IDE6MykpKQ0KbGV2ZWxzKGF2Z19zZXJpZXNbWyJSb29tIl1dKSA8LSBwYXN0ZSgiU2FsbGUiLCAxOjMsIHNlcCA9ICIiKQ0KDQojIEFmZmljaGFnZSBzb3VzIGZvcm1lIGRlIHBsb3QgaW50ZXJhY3RpZiBkZSBtYW5pw6hyZSBhdXRvbWF0aXPDqWUNCmdncGxvdGx5KGdncGxvdChkYXRhID0gYXZnX3NlcmllcywgYWVzX3N0cmluZyh4ID0gIlRpbWUiLCB5ID0gIlN0cmVuZ3RoIiwgZ3JvdXAgPSAiTGFiZWwiLCBjb2xvciA9ICJMYWJlbCIpKSArIGdlb21fbGluZSgpICsgZ2VvbV9wb2ludCgpICsgc2NhbGVfY29sb3JfYnJld2VyKHBhbGV0dGUgPSAiU2V0MiIpICsgdGhlbWVfYncoKSArIGZhY2V0X2dyaWQoQW5jaG9yIH4gUm9vbSkgKyBsYWJzKHRpdGxlID0gIkV2b2x1dGlvbiBkZSBsYSBmb3JjZSBkdSBzaWduYWwgZGUgbCdhbmNyZSBwYXIgcmFwcG9ydCBhdSB0ZW1wcyIpLCB3aWR0aCA9IDk2MCwgaGVpZ2h0ID0gNzIwKQ0KDQojIEVucmVnaXN0cmVtZW50IGRlIGxhIHRhYmxlIHBvdXIgdXNhZ2UgdWx0w6lyaWV1ciBzaSBuw6ljZXNzYWlyZQ0KZndyaXRlKGF2Z19zZXJpZXMsICJhZ3JlZ2F0aW9uL2FncmVnYXRpb24xLmNzdiIpDQoNCiMgVGVtcHMgbsOpY2Vzc2FpcmUNCnRpbWluZyhDdXJyZW50VGltZSwgIkFncsOpZ2F0aW9uIGRlcyBkb25uw6llcyIpDQpgYGANCg0KIyMjIENyw6lhdGlvbiBkZSBmZWF0dXJlcw0KDQpQb3VyIGNyw6llciB1biBiZW5jaG1hcmsgcmFwaWRlIGJhc8OpIHN1ciBsZXMgZG9ubsOpZXMgZXQgcydhY2NvbW9kZXIgZGVzIHRhaWxsZXMgZGlmZsOpcmVudGVzIGRlcyB2YXJpYWJsZXMsIG9uIGNyw6nDqSBsZXMgZGlmZsOpcmVudGVzICgzNikgZmVhdHVyZXMgOg0KDQotIENvZWZmaWNpZW50IGxpbsOpYWlyZXMgbXVsdGlwbGljYXRldXJzIHBhciBtb2TDqGxlIGxpbsOpYWlyZSBlbnRyZSBjaGFxdWUgYW5jcmUgKHRyb2lzIGFuY3JlcyBlc3RpbWVudCB1bmUgYW5jcmUpICgxNiBmZWF0dXJlcykgPT4gY2VzIGZlYXR1cmVzIGRvaXZlbnQgcG91dm9pciByw6lwb25kcmUgw6AgbGEgZGlyZWN0aW9uIGxpbsOpYWlyZSBwcm9iYWJsZSBkZSBsYSBwZXJzb25uZSwgcGFyIGFuY3JlDQotIFLDqXNpZHVzIGRlcyBtb2TDqGxlcyBsaW7DqWFpcmVzICgxNiBmZWF0dXJlcykgPT4gY2VzIGZlYXR1cmVzIGRvaXZlbnQgYW5ub25jZXIgbCdpbmNlcnRpdHVkZSBsaW7DqWFpcmUgZGUgbGEgcGVyc29ubmUsIHBhciBhbmNyZQ0KLSBMZSBkZXJuaWVyIHBvaW50IGF2YW50IGQnb2JzZXJ2YXRpb24gZW5yZWdpc3Ryw6kgKDQgZmVhdHVyZXMpID0+IGNlcyBmZWF0dXJlcyBkb2l2ZW50IHBlcm1ldHRyZSBkZSBwb3NpdGlvbm5lciBsYSBwZXJzb25uZSBqdXN0ZSBhdmFudCBzYSB0cmFqZWN0b2lyZSBmdXR1cmUNCg0KT24gw6l2aXRlcmEgZGUgdGVudGVyIGRlIHByw6lkaXJlIGxlIG1vdXZlbWVudCBkJ3VuZSBwZXJzb25uZSBhdSBuaXZlYXUgbG9jYWwgOCBzZWNvbmRlcyBwbHVzIHRhcmQsIHB1aXNxdSdvbiBuZSBkaXNwb3NlIHBhcyBmb3Jjw6ltZW50IGQndW4gamV1IGRlIGRvbm7DqWVzIGxhcmdlIHBhciBzw6lyaWUgdGVtcG9yZWxsZSAoc2FucyBvdWJsaWVyIHF1ZSBsZXMgcXVhdHJlIGFuY3JlcyBzb250IGTDqXBlbmRlbnRlcyBsJ3VuZSBkZSBsJ2F1dHJlLCBkb25jIHByw6lkaXJlIGwndW5lIG7DqWNlc3NpdGUgZGUgcHLDqWRpcmUgbGVzIHRyb2lzIGF1dHJlcyDDqWdhbGVtZW50IGV0IHNpbXVsdGFuw6ltZW50IHNpIHBvc3NpYmxlKS4NCg0KYGBge3IgQ3JlYXRpb25GZWF0dXJlMX0NCiMgQ29tcHRldXIgZGUgdGVtcHMNCkN1cnJlbnRUaW1lIDwtIHRpbWVyKCkgIyBDaHVuayBDcsOpYXRpb24gZGVzIGZlYXR1cmVzDQoNCiMgUHLDqS1pbml0aWFsaXNhdGlvbiBkZSBsYSBmcmFtZQ0KbWluaV9sbSA8LSBkYXRhLmZyYW1lKG1hdHJpeChucm93ID0gMzE0LCBuY29sID0gMzYpKQ0KDQojIEJvdWNsZSBwYXIgc8OpcmllIHRlbXBvcmVsbGUNCmZvciAoaSBpbiAxOjMxNCkgew0KICANCiAgIyBCb3VjbGUgcGFyIGFuY3JlDQogIGZvciAoaiBpbiAxOjQpIHsNCiAgICANCiAgICAjIEVudHJhaW5lbWVudCBkJ3VuIG1vZMOobGUgbGluw6lhaXJlIHV0aWxpc2FudCBsZXMgYXV0cmVzIGFuY3JlcywgYXZlYyBsJ2ludGVyY2VwdHJpY2UNCiAgICB0ZW1wX21vZGVsIDwtIGZhc3RMbVB1cmUoWCA9IGNiaW5kKGFzLm1hdHJpeChkYXRhX3ByZVtbaV1dWywgKDE6NClbLWpdLCB3aXRoID0gRkFMU0VdKSwgcmVwKDEsIG5yb3coZGF0YV9wcmVbW2ldXSkpKSwgeSA9IGRhdGFfcHJlW1tpXV1bW2pdXSkNCiAgICANCiAgICAjIEVucmVnaXN0cmVtZW50IGRlcyBjb2VmZmljaWVudHMgZXQgZGVzIHLDqXNpZHVzDQogICAgbWluaV9sbVtpLCAoaiAqIDggLSA3KTooaiAqIDgpXSA8LSBjKHRlbXBfbW9kZWwkY29lZmZpY2llbnRzLCB0ZW1wX21vZGVsJHN0ZGVycikNCiAgICANCiAgfQ0KICANCiAgIyBBam91dCBkdSBkZXJuaWVyIMOpbMOpbWVudCBkZSBsYSBzw6lyaWUgdGVtcG9yZWxsZSAoNCBhbmNyZXMpDQogIG1pbmlfbG1baSwgMzM6MzZdIDwtIGRhdGFfcHJlW1tpXV1bbnJvdyhkYXRhX3ByZVtbaV1dKSwgXQ0KICANCn0NCg0KIyBFbnJlZ2lzdHJlbWVudCBkZXMgZG9ubsOpZXMgYXUgZm9ybWF0IENTVg0KZndyaXRlKGNiaW5kKG1pbmlfbG0sIEdyb3VwID0gZ3JvdXBfcm9vbVtbImRhdGFzZXRfSUQiXV0sIExhYmVsID0gZ3JvdXBfcGF0aFtbInBhdGhfSUQiXV0pLCAiZmVhdHVyZXMvZmVhdHVyZXMxLmNzdiIpDQoNCiMgVGVtcHMgbsOpY2Vzc2FpcmUNCnRpbWluZyhDdXJyZW50VGltZSwgIkNyw6lhdGlvbiBkZXMgZmVhdHVyZXMiKQ0KYGBgDQoNCiMjIyBGZWF0dXJlIE1hcA0KDQpPbiBlbnJlZ2lzdHJlIGxlIG5vbSBkZXMgZmVhdHVyZXMgZGFucyB1biBmaWNoaWVyIMOgIHBhcnQgKEZlYXR1cmUgbWFwID0gIlZlY3RldXIgZGUgZG9ubsOpZXMgPT4gRXNwYWNlIGRlIGZlYXR1cmVzIiwgaWNpIHVuZSBjb2xvbm5lID0gdW5lIGZlYXR1cmUpLiBJbCBlc3QgdXRpbGUgZGUgZmFpcmUgY29ycmVzcG9uZHJlIGRlcyBjb2xvbm5lcyDDoCBkZXMgdmFyaWFibGVzLCBzdXJ0b3V0IGxvcnNxdWUgcGFyZm9pcyBvbiB1dGlsaXNlIGRlcyB0ZWNobmlxdWVzIGRlIHJlbWFwcGluZyAoZXhlbXBsZSA6IFtrZXJuZWwgdHJpY2tdKGh0dHBzOi8vbWVkaXVtLmNvbS9ATGF1cmFlMi91c2luZy15b3VyLWJyYWluLWZvci1zbWFydC10cmFuc2Zvcm1hdGlvbnMtb2YtZGF0YS04ZmE4NTNiY2YzNTAjLmNkeXE1dGJ0NykpLg0KDQpgYGB7ciBGZWF0dXJlTWFwfQ0KY2F0KGMocGFzdGUwKHJlcChjKHBhc3RlMCgiQ29lZiIsIDE6NCksIHBhc3RlMCgiUsOpc2kiLCAxOjQpKSwgNCksIHBhc3RlMCgiXyIsIGludmVyc2UucmxlKGxpc3QobGVuZ3RocyA9IHJlcCg4LCA0KSwgdmFsdWVzID0gMTo0KSkpKSwgcGFzdGUwKCJQb3NJbml0aWFsZV8iLCAxOjQpKSwgc2VwID0gIiwgIiwgZmlsZSA9ICJmZWF0dXJlcy9tYXAuY3N2IiwgYXBwZW5kID0gRkFMU0UpDQpgYGANCg0KIyBQcmVtacOocmUgQW5hbHlzZSBTeXN0w6ltaXF1ZQ0KDQpMJ2FuYWx5c2Ugc3lzdMOpbWlxdWUgcG9ydGUgc3VyIGxhIHBlcmZvcm1hbmNlIGRlcyBtb2TDqGxlcyBlbiB1dGlsaXNhbnQgbGV1cnMgcGFyYW3DqHRyZXMgcGFyIGTDqWZhdXQsIGVuIGNvdXZyYW50IHNpIHBvc3NpYmxlIHBsdXNpZXVycyBkaWZmw6lyZW50IHR5cGVzIGRlIG1vZMOobGVzIDoNCg0KYGBge3IgVGFibGVhdU1vZGVsZXMsIGVjaG89RkFMU0UsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmdzPUZBTFNFLCByZXN1bHRzPSdhc2lzJ30NCiMgUGFzIGQnYXV0cmVzIG1veWVucyBzaW1wbGVzIGNyb3NzLXBsYXRlZm9ybWVzICsgY3Jvc3MtZmljaGllcnMNCnRlbXBfcHJpbnQgPC0gInwgVHlwZSBkZSBtb2TDqGxlIHwgRGVzY3JpcHRpb24gfCBXcmFwcGVyIHwNCnwtLS0tLS0tLS0tLS0tLS0tLS18LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS18Oi0tLS0tLS0tLS0tLS0tLS0tLS06fA0KfCBMaW7DqWFpcmUgfCBSw6lncmVzc2lvbiBsb2dpc3RpcXVlIChvbiB1dGlsaXNlcmEgbGUgbW9kw6hsZSBsaW7DqWFpcmUgZ8OpbsOpcmFsaXPDqSkgfCB4Z2Jvb3N0IChDKyspLCBIMk8gKEphdmEpIHwNCnwgTm9uLWxpbsOpYWlyZSBzYW5zIGVuc2VtYmxlIHwgQXJicmUgZGUgZMOpY2lzaW9uIChvbiB1dGlsaXNlcmEgdW5lIGl0w6lyYXRpb24gZGUgYm9vc3RpbmcgZCdhcmJyZXMgZGUgZMOpY2lzaW9uKSB8IHhnYm9vc3QgKEMrKyksIEgyTyAoSmF2YSkgfA0KfCBOb24tbGluw6lhaXJlIGQndW5kZXJmaXR0aW5nIHwgQXJicmUgZGUgZMOpY2lzaW9uIChvbiB1dGlsaXNlcmEgbGUgbW9kw6hsZSBkZSBSYW5kb20gRm9yZXN0KSB8IHhnYm9vc3QgKEMrKyksIEgyTyAoSmF2YSkgfA0KfCBOb24tbGluw6lhaXJlIGQnb3ZlcmZpdHRpbmcgfCBBcmJyZSBkZSBkw6ljaXNpb24gKG9uIHV0aWxpc2VyYSBsZSBtb2TDqGxlIGQnYXJicmVzIGRlIGTDqWNpc2lvbiBib29zdMOpKSB8IHhnYm9vc3QgKEMrKyksIEgyTyAoSmF2YSkgfA0KfCBMaW7DqWFpcmUgZCdvdmVyZml0dGluZyB8IFLDqXNlYXV4IGRlIG5ldXJvbmVzIChvbiB0ZXN0ZXJhIHVuZSBhcmNoaXRlY3R1cmUgMzJ4NiArIFJlTFUgLyBUYW5oLCAxNngxNng2ICsgUmVMVSAvIFRhbmgpIHwgSDJPIChKYXZhKSB8Ig0KY2F0KHRlbXBfcHJpbnQpICMgQWZmaWNoZSBsYSB0YWJsZSB0ZWwgcXVlbCBhdSBmb3JtYXQgTWFya2Rvd24NCmBgYA0KDQpQYXIgc2ltcGxpY2l0w6ksIG9uIHV0aWxpc2VyYSB4Z2Jvb3N0LCBxdWkgZXN0IHVuIG1vZMOobGUgY29kw6kgZW4gQysrIGRvbnQgbGEgdml0ZXNzZSBkZSBjYWxjdWwgKGV0IGwnZXNzZW50aWVsIGRlIGxhIHBhcmFtw6l0cmlzYXRpb24pIGVzdCBpbsOpZ2Fsw6llIChtaXMgw6AgcGFydCBMaWdodEdCTSBwYXIgTWljcm9zb2Z0KSwgYWluc2kgcXVlIEgyTyBjb2TDqSBlbiBKYXZhIChyZWxhdGl2ZW1lbnQgYmVhdWNvdXAgcGx1cyBsZW50LCBtw6ptZSBzaSBwbHVzIHJhcGlkZSBxdWUgbGEgcGx1cGFydCBkZXMgbGlicmFyaWVzIGRlIG1hY2hpbmUgbGVhcm5pbmcpLiBOb3VzIG5lIHNhdXZlZ2FyZG9ucyBwYXMgZW5jb3JlIGRlIG1vZMOobGVzLiBDZXMgZGV1eCBsaWJyYXJpZXMgZGlzcG9zZW50IGRlIG1vZMOobGVzIGV4cG9ydGFibGVzIHF1aSBwZXV2ZW50IMOqdHJlIGVucmVnaXN0csOpcyBldCBkw6lwbG95w6lzIHJlc3BlY3RpdmVtZW50IGVuIEMvc2hlbGwgKHhnYm9vc3QpIGV0IEphdmEgKEgyTykgcG91ciB1biB1c2FnZSBpbW3DqWRpYXQuDQoNCk5vdXMgbid1dGlsaXNvbnMgcGFzIHVuIEtOTiAodm9pc2lucyBsZXMgcGx1cyBwcm9jaGVzKSBuaSB1biBTVk0gKFN1cHBvcnQgVmVjdG9yIE1hY2hpbmVzKSBwb3VyIGxldXJzIHByb2Jsw6htZXMgZCdlbnRyYWluZW1lbnQgZXQgZGUgZMOpcGxvaWVtZW50IChLTk4gZMOpZsOocmUgbCdlbnRyYWluZW1lbnQgw6AgbGEgcHLDqWRpY3Rpb24sIGV0IFNWTSBhIHVuZSBjb21wbGV4aXTDqSBkZSBjYWxjdWwgdGVsbGUgcXVlIGNlIG5lIHNvbnQgcGFzIGRlcyBtb2TDqGxlcyBkw6lwbG95YWJsZXMgZGUgbWFuacOocmUgZWZmaWNpZW50ZSBlbiBlbnRyZXByaXNlKS4NCg0KTGVzIG9iamVjdGlmcyBzZXJvbnQgbGVzIHN1aXZhbnRzIDoNCg0KLSBNaW5pbWlzZXIgbCdlcnJldXIgZGUgY2xhc3NpZmljYXRpb24NCi0gTmUgcGFzIGTDqXBhc3NlciAyNSUgZGUgZmVhdHVyZXMgKDUyKSBwYXIgcmFwcG9ydCBhdSBub21icmUgZCdvYnNlcnZhdGlvbnMgcGFyIGZvbGQgcG91ciDDqXZpdGVyIGxlIHN1cmFwcHJlbnRpc3NhZ2UgZGVzIGZlYXR1cmVzIChxdWkgbmUgc2UgcmVww6lyZXJhIHBhcyBzdXIgbGVzIG3DqXRyaXF1ZXMgZGUgdmFsaWRhdGlvbiBhdmVjIHNpIHBldSBkJ8OpY2hhbnRpbGxvbnMsIHF1aSBlc3QgbGUgcHJvYmzDqG1lIGRlIGZsdWN0dWF0aW9uIHBhciBlcnJldXIpDQotIETDqXBhc3NlciBsZSBzZXVpbCBkdSBtb2TDqGxlICJhbMOpYXRvaXJlIiAoNTMvMjEwICsgNTMvMjA4ICsgNTIvMjEwLCBzb2l0IGByIHNwcmludGYoIjA1LjAyZiIsIDEwMCAqICg1My8yMTAgKyA1My8yMDggKyA1Mi8yMTApKWAlKQ0KDQpPbiBwcsOpcGFyZSBkJ2Fib3JkIGxlcyBwcsOpLXJlcXVpcyBwb3VyIGxlIGNhbGN1bCwgcXVpIHNvbnQgOg0KDQotIExlcyBmb2xkcyDDoCB1dGlsaXNlciBwb3VyIGxhIGNyb3NzLXZhbGlkYXRpb24gOiBvbiBkw6ljb3VwZXJhIGRlIG1hbmnDqHJlIGRpc2pvaW50ZSBsZXMgc2FsbGVzLCBhZmluIGQnw6l2aXRlciB0b3V0IGxlYWthZ2Ugw6AgbCdpbnTDqXJpZXVyIGRlcyBzYWxsZXMgKGFmaW4gZCdlbXDDqmNoZXIgbGUgbW9kw6hsZSBkZSBtYWNoaW5lIGxlYXJuaW5nIGQnYXBwcmVuZHJlIGxlcyBzYWxsZXMgZXQgbm9uIHBhcyBsYSBnw6luw6lyYWxpc2F0aW9uIMOgIGRlcyDDqWNoYW50aWxsb24gaW5jb25udXMgZGFucyBkZSBub3V2ZWxsZXMgc2FsbGVzKQ0KLSBMYSBtw6l0cmlxdWUgZCfDqXZhbHVhdGlvbiwgb8O5IGljaSBsJ2V4YWN0aXR1ZGUgZGVzIHByw6lkaWN0aW9ucyBlc3QgbXVsdGktY2xhc3NlDQoNCkNvbW1lIG5vdXMgYWxsb25zIGTDqWNvdXBlciBzZWxvbiBkaWZmw6lyZW50ZXMgbcOpdGhvZGVzLCBub3VzIGRldnJvbnMgY3LDqWVyIMOgIGxhIGZvaXMgbGVzIGZvbGQgZGUgdHJhaW4gbWFpcyBhdXNzaSBkZSB0ZXN0IDoNCg0KLSBFbnRyYWluZW1lbnQgc3VyIHVuZSBzYWxsZSwgcHLDqWRpY3Rpb24gc3VyIHVuZSBhdXRyZSBzYWxsZSAoMSBjb250cmUgMSkNCi0gRW50cmFpbmVtZW50IHN1ciB1bmUgc2FsbGUsIHByw6lkaWN0aW9uIHN1ciBkZXV4IGF1dHJlcyBzYWxsZXMgKDEgY29udHJlIDIpDQotIEVudHJhaW5lbWVudCBzdXIgZGV1eCBzYWxsZXMsIHByw6lkaWN0aW9uIHN1ciB1bmUgYXV0cmUgc2FsbGUgKDIgY29udHJlIDEpDQoNCkxhIG1veWVubmUgZ8OpbsOpcmFsZSBzZXJhIGFncsOpZ8OpZSDDoCBwYXJ0aXIgZGVzIHRyb2lzIG1veWVubmVzIGFncsOpZ8OpZXMgKDEgY29udHJlIDEsIDEgY29udHJlIDIsIGV0IDIgY29udHJlIDEpLiBBdSB0b3RhbCwgbm91cyBhdm9ucyAxNDQgbW9kw6hsZXMgKDEyIG1vZMOobGVzLCAxMiBzZXRzIGQnZW50cmFpbmVtZW50L3ZhbGlkYXRpb24pIMOgIHRlc3Rlci4gVnUgbGEgdGFpbGxlIGRlcyBkb25uw6llcyAoMzE0IG9ic2VydmF0aW9ucywgbW9pbnMgZCd1bmUgY2VudGFpbmUgZGUgZmVhdHVyZXMpIGV0IGxhIG3DqW1vaXJlIHZpdmUgZGlzcG9uaWJsZSAocGx1cyBkZSA1MCBHQiksIG9uIHBldXQgc2UgcGVybWV0dHJlIGRlIHByw6ktY3LDqWVyIHRvdXRlcyBsZXMgZGF0YXNldHMgYXUgbGlldSBkZSBsZXMgY3LDqWVyIMOgIGxhIHZvbMOpZS4NCg0KTGUgdGVtcHMgcGVyZHUgw6AgbGEgY3LDqWF0aW9uIGRlcyBkYXRhc2V0cyBpY2kgZXN0IGR1ZSBlbiBncmFuZGUgcGFydGllIGR1ZSDDoCBIMk8sIGRvbnQgbGVzIG9iamV0cyBkb2l2ZW50IMOqdHJlIHRyYWR1aXRzIGVuIEphdmEgw6AgcGFydGlyIGRlIFIuDQoNCkxlcyBmaWNoaWVycyBzb250IGVucmVnaXN0csOpcyBhdmVjIGxhIG5vbWVuY2xhdHVyZSBzdWl2YW50ZSA6DQoNCi0gTkwgcG91ciAiTm8gTGFiZWwiIChkb25uw6llcyBub24gbGFiZWxsaXPDqWVzKQ0KLSBMIHBvdXIgIkxhYmVsIiAoZG9ubsOpZXMgbGFiZWxsaXPDqWVzKQ0KLSAuY3N2IHBvdXIgbGUgZm9ybWF0IENTViAoQ29tbWEtU2VwYXJhdGVkIFZhbHVlcykNCi0gLmRhdGEgcG91ciBsZSBmb3JtYXQgYmluYWlyZSB4Z2Jvb3N0IChMaWdodFNWTSBjb21wcmVzc8OpKQ0KDQpgYGB7ciBHZW5lcmF0aW9uRG9ubmVlczF9DQojIENvbXB0ZXVyIGRlIHRlbXBzDQpDdXJyZW50VGltZSA8LSB0aW1lcigpICMgQ2h1bmsgUHLDqXBhcmF0aW9uIGRlIGwnw6l2YWx1YXRpb24gZHUgbW9kw6hsZQ0KDQojIE/DuSBzYXV2ZWdhcmRlciBsZXMgZmljaGllcnMgPw0KZmlsZV90YWcgPC0gIjFfZGF0YS8iDQoNCiMgSW5pdGlhbGlzYXRpb24gZGUgbGEgdmFyaWFibGUgcXVpIGFjY3VlaWxsZXJhIGxhIHByw6ljaXNpb24NCmFjY3VyYWN5IDwtIGRhdGEuZnJhbWUobWF0cml4KG5yb3cgPSAxNiwgbmNvbCA9IDEzKSkNCmNvbG5hbWVzKGFjY3VyYWN5KSA8LSBjKCJGb2xkIiwgInhnYl9MaW5lYXJNb2RlbCIsICJ4Z2JfRGVjaXNpb25UcmVlIiwgInhnYl9SYW5kb21Gb3Jlc3QiLCAieGdiX0dyYWRpZW50Qm9vc3RpbmciLCAiaDJvX0xpbmVhck1vZGVsIiwgImgyb19EZWNpc2lvblRyZWUiLCAiaDJvX1JhbmRvbUZvcmVzdCIsICJoMm9fR3JhZGllbnRCb29zdGluZyIsICJoMm9fTk5fMzJ4Nl9SZUxVIiwgImgyb19OTl8zMng2X1NvZnQiLCAiaDJvX05OXzE2eDE2eDZfUmVMVSIsICJoMm9fTk5fMTZ4MTZ4Nl9Tb2Z0IikNCmFjY3VyYWN5WywgMV0gPC0gYygiRm9sZF8xdjIiLCAiRm9sZF8xdjMiLCAiRm9sZF8ydjEiLCAiRm9sZF8ydjMiLCAiRm9sZF8zdjEiLCAiRm9sZF8zdjIiLCAiRm9sZF8xdjIzIiwgIkZvbGRfMnYxMyIsICJGb2xkXzN2MTIiLCAiRm9sZF8xMnYzIiwgIkZvbGRfMTN2MiIsICJGb2xkXzIzdjEiLCAiTW95ZW5uZV8xYzEiLCAiTW95ZW5uZV8xYzIiLCAiTW95ZW5uZV8yYzEiLCAiTW95ZW5uZSIpDQoNCiMgSW5pdGlhbGlzYXRpb24gZGVzIGZvbGRzIHBvdXIgbGEgY3Jvc3MtdmFsaWRhdGlvbg0KZm9sZHNfdHJhaW4gPC0gbGlzdCgpDQpmb2xkc190ZXN0IDwtIGxpc3QoKQ0KdHJhaW5pbmdfZGF0YSA8LSBsaXN0KCkNCnRlc3RpbmdfZGF0YSA8LSBsaXN0KCkNCnRyYWluaW5nX3hnYiA8LSBsaXN0KCkNCnRlc3RpbmdfeGdiIDwtIGxpc3QoKQ0KdHJhaW5pbmdfaDJvIDwtIGxpc3QoKQ0KdGVzdGluZ19oMm8gPC0gbGlzdCgpDQpjb21iaW5hdGlvbnNfdHJhaW4gPC0gYyhsaXN0KDEsIDEsIDIsIDIsIDMsIDMpLCBjb21ibigzLCAxLCBzaW1wbGlmeSA9IEZBTFNFKSwgY29tYm4oMywgMiwgc2ltcGxpZnkgPSBGQUxTRSkpDQpjb21iaW5hdGlvbnNfdGVzdCA8LSBjKGxpc3QoMiwgMywgMSwgMywgMSwgMiksIHJldihjb21ibigzLCAyLCBzaW1wbGlmeSA9IEZBTFNFKSksIHJldihjb21ibigzLCAxLCBzaW1wbGlmeSA9IEZBTFNFKSkpDQp0ZW1wX2ZhY3RvcnMgPC0gYXMuZmFjdG9yKGdyb3VwX3BhdGgkcGF0aF9JRCkNCg0KIyBDcsOpYXRpb24gZGVzIGRvbm7DqWVzIGQnZW50cmFpbmVtZW50IGV0IGRlIHZhbGlkYXRpb24NCmZvciAoaSBpbiAxOjEyKSB7DQogIA0KICAjIENyw6lhdGlvbiBkZXMgZm9sZHMgZCdlbnRyYWluZW1lbnQgZXQgZGUgdmFsaWRhdGlvbg0KICBmb2xkc190cmFpbltbaV1dIDwtIHdoaWNoKGdyb3VwX3Jvb21bWyJkYXRhc2V0X0lEIl1dICVpbiUgY29tYmluYXRpb25zX3RyYWluW1tpXV0pDQogIGZvbGRzX3Rlc3RbW2ldXSA8LSB3aGljaChncm91cF9yb29tW1siZGF0YXNldF9JRCJdXSAlaW4lIGNvbWJpbmF0aW9uc190ZXN0W1tpXV0pDQogIA0KICAjIFJlY2hlcmNoZSBldCBzdXBwcmVzc2lvbiBkdSBsYWJlbCAzIGxvcnNxdWUgbGEgc2FsbGUgMSBlc3QgaXNvbMOpZSAoc29pdCBlbiB0cmFpbiBvbiBlbmzDqHZlIGVuIHRlc3QsIHNvaXQgZW4gdGVzdCBvbiBlbmzDqHZlIGVuIHRyYWluKQ0KICBpZiAoKGxlbmd0aChjb21iaW5hdGlvbnNfdHJhaW5bW2ldXSkgPT0gMSkgJiAoY29tYmluYXRpb25zX3RyYWluW1tpXV1bMV0gPT0gMSkpIHsNCiAgICBmb2xkc190ZXN0W1tpXV0gPC0gZm9sZHNfdGVzdFtbaV1dW2dyb3VwX3BhdGgkcGF0aF9JRFtmb2xkc190ZXN0W1tpXV1dICE9IDNdDQogIH0NCiAgaWYgKChsZW5ndGgoY29tYmluYXRpb25zX3Rlc3RbW2ldXSkgPT0gMSkgJiAoY29tYmluYXRpb25zX3Rlc3RbW2ldXVsxXSA9PSAxKSkgew0KICAgIGZvbGRzX3RyYWluW1tpXV0gPC0gZm9sZHNfdHJhaW5bW2ldXVtncm91cF9wYXRoJHBhdGhfSURbZm9sZHNfdHJhaW5bW2ldXV0gIT0gM10NCiAgfQ0KICANCiAgIyBDcsOpYXRpb24gZGVzIGRvbm7DqWVzIGQnZW50cmFpbmVtZW50IGV0IGRlIHZhbGlkYXRpb24NCiAgdHJhaW5pbmdfZGF0YVtbaV1dIDwtIG1pbmlfbG1bZm9sZHNfdHJhaW5bW2ldXSwgXQ0KICB0ZXN0aW5nX2RhdGFbW2ldXSA8LSBtaW5pX2xtW2ZvbGRzX3Rlc3RbW2ldXSwgXQ0KICANCiAgIyBFbnJlZ2lzdHJlbWVudCBkZXMgZG9ubsOpZXMgQ1NWDQogIGZ3cml0ZSh0cmFpbmluZ19kYXRhW1tpXV0sIHBhc3RlMChmaWxlX3RhZywgInRyYWluTkxfIiwgc3ByaW50ZigiJTAyZCIsIGkpLCAiLmNzdiIpKQ0KICBmd3JpdGUodGVzdGluZ19kYXRhW1tpXV0sIHBhc3RlMChmaWxlX3RhZywgInRlc3ROTF8iLCBzcHJpbnRmKCIlMDJkIiwgaSksICIuY3N2IikpDQogIA0KICAjIFRyYW5zZm9ybWF0aW9uIGRlcyBkb25uw6llcyBhdSBmb3JtYXQgYXBwcm9wcmnDqSBwb3VyIHhnYm9vc3QNCiAgdHJhaW5pbmdfeGdiW1tpXV0gPC0geGdiLkRNYXRyaXgoZGF0YSA9IGFzLm1hdHJpeCh0cmFpbmluZ19kYXRhW1tpXV0pLCBsYWJlbCA9IGdyb3VwX3BhdGgkcGF0aF9JRFtmb2xkc190cmFpbltbaV1dXSAtIDEpDQogIHRlc3RpbmdfeGdiW1tpXV0gPC0geGdiLkRNYXRyaXgoZGF0YSA9IGFzLm1hdHJpeCh0ZXN0aW5nX2RhdGFbW2ldXSksIGxhYmVsID0gZ3JvdXBfcGF0aCRwYXRoX0lEW2ZvbGRzX3Rlc3RbW2ldXV0gLSAxKQ0KICANCiAgIyBEdW1waW5nIGRlcyBkYXRhc2V0cyBiaW5haXJlcyB4Z2Jvb3N0DQogIHhnYi5ETWF0cml4LnNhdmUodHJhaW5pbmdfeGdiW1tpXV0sIHBhc3RlMChmaWxlX3RhZywgInRyYWluTF8iLCBzcHJpbnRmKCIlMDJkIiwgaSksICIuZGF0YSIpKQ0KICB4Z2IuRE1hdHJpeC5zYXZlKHRlc3RpbmdfeGdiW1tpXV0sIHBhc3RlMChmaWxlX3RhZywgInRlc3RMXyIsIHNwcmludGYoIiUwMmQiLCBpKSwgIi5kYXRhIikpDQogIA0KICAjIFRyYW5zZm9ybWF0aW9uIGRlcyBkb25uw6llcyBhdSBmb3JtYXQgYXBwcm9wcmnDqSBwb3VyIEgyTw0KICB0cmFpbmluZ19oMm9bW2ldXSA8LSBhcy5oMm8oY2JpbmQoTGFiZWwgPSB0ZW1wX2ZhY3RvcnNbZm9sZHNfdHJhaW5bW2ldXV0sIHRyYWluaW5nX2RhdGFbW2ldXSkpDQogIHRlc3RpbmdfaDJvW1tpXV0gPC0gYXMuaDJvKGNiaW5kKExhYmVsID0gdGVtcF9mYWN0b3JzW2ZvbGRzX3Rlc3RbW2ldXV0sIHRlc3RpbmdfZGF0YVtbaV1dKSkNCiAgDQogICMgRW5yZWdpc3RyZW1lbnQgZGVzIGZyYW1lcyBIMk8gKENTViArIExhYmVsKQ0KICBoMm8uZXhwb3J0RmlsZSh0cmFpbmluZ19oMm9bW2ldXSwgcGFzdGUwKGZpbGVfdGFnLCAidHJhaW5MXyIsIHNwcmludGYoIiUwMmQiLCBpKSwgIi5jc3YiKSwgZm9yY2UgPSBUUlVFKQ0KICBoMm8uZXhwb3J0RmlsZSh0ZXN0aW5nX2gyb1tbaV1dLCBwYXN0ZTAoZmlsZV90YWcsICJ0ZXN0TF8iLCBzcHJpbnRmKCIlMDJkIiwgaSksICIuY3N2IiksIGZvcmNlID0gVFJVRSkNCiAgDQp9DQoNCiMgVGVtcHMgbsOpY2Vzc2FpcmUNCnRpbWluZyhDdXJyZW50VGltZSwgIlByw6lwYXJhdGlvbiBkZSBsJ8OpdmFsdWF0aW9uIGR1IG1vZMOobGUiKQ0KYGBgDQoNCkRhbnMgbGUgY2FzIG/DuSBvbiBzb3VoYWl0ZXJhaXQgZmFpcmUgdW5lIGNsYXNzaWZpY2F0aW9uIGJpbmFpcmUsIHV0aWxpc2VyIHVuIHNldWlsIGR5bmFtaXF1ZSBwb3VyIGTDqWNvdXBlciBsZXMgbGFiZWxzIGVzdCBwbHVzIGFkw6lxdWF0IChwYXIgcmFwcG9ydCDDoCAwLjUwIGNvbW1lIHNldWlsKS4gT24gbmUgcsOpYWxpc2UgcGFzIGRlIGNsYXNzaWZpY2F0aW9uIGJpbmFpcmUgaWNpLCBtYWlzIGNlbGEgcGV1dCB0b3Vqb3VycyBzZXJ2aXIgZGFucyBsZSBmdXR1ci4NCg0KYGBge3IgQmluYXJ5QWNjdXJhY3l9DQojIExlIGJhY2tlbmQgdXRpbGlzw6kgcGFyIHhnYm9vc3QgcG91ciBkw6l0ZXJtaW5lciBsZSBzZXVpbCDDoCB1dGlsaXNlciBwb3VyIHBhcnRhZ2VyIGxlcyBsYWJlbHMgMCBldCAxDQphY2NfZXZhbCA8LSBmdW5jdGlvbihwcmVkLCBkdHJhaW4pIHsNCiAgDQogICMgUsOpY3Vww6lyYXRpb24gZHUgbGFiZWwNCiAgeV90cnVlIDwtIGdldGluZm8oZHRyYWluLCAibGFiZWwiKQ0KICANCiAgIyBDcsOpYXRpb24gZGUgbGEgZGF0YS50YWJsZSB0cmnDqWUgYXZlYyBjb21tZSBjbMOpIHByaW1haXJlIGxhIHByb2JhYmlsaXTDqQ0KICBEVCA8LSBkYXRhLnRhYmxlKHlfdHJ1ZSA9IHlfdHJ1ZSwgeV9wcm9iID0gcHJlZCwga2V5ID0gInlfcHJvYiIpDQogIA0KICAjIFByw6lwYXJhdGlvbiBwb3VyIGxlIG5ldHRveWFnZSBkZXMgZG91YmxvbnMgcG9zdMOpcmlldXJzDQogIGNsZWFuZXIgPC0gIWR1cGxpY2F0ZWQoRFRbLCAieV9wcm9iIl0sIGZyb21MYXN0ID0gVFJVRSkNCiAgDQogICMgUHLDqS1jYWxjdWwgZGUgdmFyaWFibGVzIHNww6ljaWZpcXVlcw0KICBsZW5zIDwtIGxlbmd0aCh5X3RydWUpDQogIG51bXAgPC0gc3VtKHlfdHJ1ZSkNCiAgDQogICMgRMOpdGVybWluYXRpb24gZGVzIHZyYWlzIG7DqWdhdGlmcyBldCBkZXMgdnJhaXMgcG9zaXRpZnMNCiAgRFRbLCB0bl92IDo9IGN1bXN1bSh5X3RydWUgPT0gMCldDQogIERUWywgdHBfdiA6PSBudW1wIC0gY3Vtc3VtKHlfdHJ1ZSA9PSAxKV0NCiAgDQogICMgTmV0dG95YWdlIGRlcyBkb3VibG9ucyBwb3VyIMOpdml0ZXIgbGUgcHJvYmzDqG1lIGQnb3JkcmUgcGFyIGNoYW5jZQ0KICBEVCA8LSBEVFtjbGVhbmVyLCBdDQogIA0KICAjIETDqXRlcm1pbmF0aW9uIGRlIGwnZXhhY3RpdHVkZSBkZXMgZG9ubsOpZXMNCiAgRFRbLCBhY2MgOj0gKHRuX3YgKyB0cF92KSAvIGxlbnNdDQogIA0KICAjIEFubnVsYXRpb24gw6AgesOpcm8gcG91ciB0b3V0ZSBvYnNlcnZhdGlvbiBkb250IGxlIGNhbGN1bCBhYm91dGl0IMOgIHVuZSBlcnJldXINCiAgRFRbLCBhY2MgOj0gaWZlbHNlKCFpcy5maW5pdGUoYWNjKSwgMCwgYWNjKV0NCiAgDQogICMgUmVjaGVyY2hlIGRlIGxhIG1laWxsZXVyZSBleGFjdGl0dWRlDQogIGJlc3Rfcm93IDwtIHdoaWNoLm1heChEVCRhY2MpDQogIGJlc3RfYWNjIDwtIHJvdW5kKDEwMCAqIERUJGFjY1tiZXN0X3Jvd1sxXV0sIGRpZ2l0cyA9IDgpDQogIA0KICAjIFJldG91ciBkZSBsYSBtZWlsbGV1cmUgZXhhY3RpdHVkZQ0KICByZXR1cm4obGlzdChtZXRyaWMgPSAiYWNjIiwgdmFsdWUgPSBiZXN0X2FjYykpDQogIA0KfQ0KYGBgDQoNCiMjIEVudHJhw65uZW1lbnQgZGVzIGRvdXplIG1vZMOobGVzDQoNCkxlIHRlbXBzIGRlIGNhbGN1bCBlc3QgcHJpbmNpcGFsZW1lbnQgZHUgYXV4IG1vZMOobGVzIEgyTyBxdWkgcHJlbm5lbnQgbGEgbWFqb3JpdMOpIGR1IHRlbXBzIGRlIGNhbGN1bCAoZW52aXJvbiAxODAgc2Vjb25kZXMgY29udHJlIHF1ZWxxdWVzIHNlY29uZGVzIHBvdXIgbGVzIDEyeDQgeGdib29zdCkuIENoYXF1ZSBtb2TDqGxlIGVzdCBlbnRyYWluw6kgc3VyIHVuIHNldCBkJ2VudHJhaW5lbWVudCwgZXQgdGVzdMOpIHN1ciB1biBzZXQgZGUgdmFsaWRhdGlvbi4NCg0KUG91ciB1dGlsaXNlciBsZXMgZmljaGllcnMgSmF2YSwgaWwgZmF1dCB1dGlsaXNlciBsZSBmaWNoaWVyIGgyby1nZW5tb2RlbC5qYXIgYWTDqXF1YXQgdHJvdXZhYmxlIGljaSA6IGh0dHA6Ly9tdm5yZXBvc2l0b3J5LmNvbS9hcnRpZmFjdC9haS5oMm8vaDJvLWdlbm1vZGVsDQoNCk5vdGUgOiBsZXMgbW9kw6hsZXMgZW5yZWdpc3Ryw6lzIG5lIHNvbnQgcGFzIGZvcmPDqW1lbnQgbGVzIG1laWxsZXVycywgbWFpcyBjb250aWVubmVudCBsZSBtb2TDqGxlIGZpbmFsIGVudHJhaW7DqSAoYXZlYyBwb3RlbnRpZWxsZW1lbnQgZGUgbCdvdmVyZml0dGluZykuDQoNCmBgYHtyIEVudHJhaW5lbWVudDEsIGNhY2hlPVRSVUV9DQojIENvbXB0ZXVyIGRlIHRlbXBzDQpDdXJyZW50VGltZSA8LSB0aW1lcigpICMgQ3LDqWF0aW9uIGV0IMOpdmFsdWF0aW9uIGRlcyBkb3V6ZSBtb2TDqGxlcyBkZSBiZW5jaG1hcmsNCg0KIyBPw7kgc2F1dmVnYXJkZXIgbGVzIGZpY2hpZXJzID8NCmZpbGVfdGFnIDwtICIxX21vZGVscy8iDQpmaWxlX2gybyA8LSAiMV9tb2RlbHMiDQoNCnhnYl9keW5hbWljX3RyYWluIDwtIGZ1bmN0aW9uKHRyYWluLCB0ZXN0LCBib29zdGVyLCBucm91bmRzLCBudW1fcGFyYWxsZWxfdHJlZXMpIHsNCiAgDQogICMgRml4YXRpb24gZHUgc2VlZCBkdSBnw6luw6lyYXRldXIgZGUgbm9tYnJlcyBhbMOpYXRvaXJlcyBwb3VyIHF1J29uIHB1aXNzZSByZXByb2R1aXJlIGxlcyByw6lzdWx0YXRzIHN1ciBkJ2F1dHJlcyBtYWNoaW5lcyBkZSBtYW5pw6hyZSBleGFjdGUNCiAgc2V0LnNlZWQoMTExMTEpDQogIA0KICAjIEVudHJhaW5lbWVudCBkdSBtb2TDqGxlDQogIHJldHVybih4Z2IudHJhaW4oZGF0YSA9IHRyYWluLA0KICAgICAgICAgICAgICAgICAgIG51bV9jbGFzcyA9IDYsICMgQ2xhc3NpZmljYXRpb24gw6AgNiBjbGFzc2VzDQogICAgICAgICAgICAgICAgICAgbnRocmVhZCA9IDEsICMgMSBjb2V1ciB1dGlsaXPDqQ0KICAgICAgICAgICAgICAgICAgIG5yb3VuZHMgPSBucm91bmRzLCAjIE5vbWJyZSBkJ2l0w6lyYXRpb25zIGRlIGJvb3N0aW5nDQogICAgICAgICAgICAgICAgICAgbnVtX3BhcmFsbGVsX3RyZWVzID0gbnVtX3BhcmFsbGVsX3RyZWVzLCAjIE5vbWJyZSBkJ2FyYnJlcyBwb3VyIGxlIG1vZGUgUmFuZG9tIEZvcmVzdA0KICAgICAgICAgICAgICAgICAgIHN1YnNhbXBsZSA9IGlmZWxzZShudW1fcGFyYWxsZWxfdHJlZXMgPiAxLCAwLjYzMiwgMSksICMgQm9vdHN0cmFwIGRlcyBkb25uw6llcyBwb3VyIGwnw6ljaGFudGlsbG9ubmFnZSBlbiBtb2RlIFJhbmRvbSBGb3Jlc3QNCiAgICAgICAgICAgICAgICAgICBldGEgPSAwLjEwLCAjIFNocmlua2FnZSBwb3VyIGxlIGJvb3N0aW5nDQogICAgICAgICAgICAgICAgICAgYm9vc3RlciA9IGJvb3N0ZXIsICMgVHlwZSBkJ2VudHJhaW5lbWVudCA6IGxpbsOpYWlyZSBvdSBub24tbGluw6lhaXJlDQogICAgICAgICAgICAgICAgICAgb2JqZWN0aXZlID0gIm11bHRpOnNvZnRwcm9iIiwgIyBHcmFkaWVudC9IZXNzaWFuIHBvdXIgbCdvcHRpbWlzYXRpb24gcGFyIEdyYWRpZW50IERlc2NlbnQNCiAgICAgICAgICAgICAgICAgICBldmFsX21ldHJpYyA9ICJtZXJyb3IiLCAjIEluZXhhY3RpdHVkZSBkZSBsYSBjbGFzc2lmaWNhdGlvbg0KICAgICAgICAgICAgICAgICAgIG1heGltaXplID0gRkFMU0UsICMgTWluaW1pc2F0aW9uIGRlIGwnZXJyZXVyDQogICAgICAgICAgICAgICAgICAgZWFybHlfc3RvcHBpbmdfcm91bmRzID0gMTAwLCAjIEFycsOqdCBhcHLDqHMgMTAwIGl0w6lyYXRpb25zIHNhbnMgYW3DqWxpb3JhdGlvbiBkZSBsYSBtw6l0cmlxdWUNCiAgICAgICAgICAgICAgICAgICB2ZXJib3NlID0gRkFMU0UsICMgU2FucyBwcmludCBkZXMgaXTDqXJhdGlvbnMNCiAgICAgICAgICAgICAgICAgICB3YXRjaGxpc3QgPSBsaXN0KHRlc3QgPSB0ZXN0KSwgIyBFc3RpbWF0aW9uIHN1ciBsZXMgZG9ubsOpZXMgZGUgdGVzdA0KICAgICAgICAgICAgICAgICAgIGNhbGxiYWNrcyA9IGxpc3QoY2IuZXZhbHVhdGlvbi5sb2coKSkpKSAjIExvZ2dpbmcgZGVzIGRvbm7DqWVzIGQnZW50cmFpbmVtZW50IHBvdXIgcG91dm9pciByw6ljdXDDqXJlciBsZXMgbcOpdHJpcXVlcw0KfQ0KDQpoMm9fbm5fdHJhaW4gPC0gZnVuY3Rpb24odHJhaW4sIHRlc3QsIG1vZGVsX2lkLCBhY3RpdmF0aW9uLCBoaWRkZW4pIHsNCiAgDQogIHJldHVybih0ZW1wX21vZGVsIDwtIGgyby5kZWVwbGVhcm5pbmcoeSA9IDEsICMgTGFiZWwgPSAxw6hyZSBjb2xvbm5lDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdHJhaW5pbmdfZnJhbWUgPSB0cmFpbiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB2YWxpZGF0aW9uX2ZyYW1lID0gdGVzdCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtb2RlbF9pZCA9IG1vZGVsX2lkLCAjIE5vbSBkdSBtb2TDqGxlDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3RhbmRhcmRpemUgPSBGQUxTRSwgIyBQYXMgZGUgc3RhbmRhcmRpc2F0aW9uIGRlcyBkb25uw6llcywgcHVpc3F1ZSBbLTEsIDFdDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYWN0aXZhdGlvbiA9IGFjdGl2YXRpb24sICMgQWN0aXZhdGlvbiBmaW5hbGUgZHUgcsOpc2VhdSBkZSBuZXVyb25lcw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhpZGRlbiA9IGhpZGRlbiwgIyBBcmNoaXRlY3R1cmUgZHUgcsOpc2VhdSBkZSBuZXVyb25lcw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGVwb2NocyA9IDEwMCwgIyBOb21icmUgZGUgcGFzc2VzDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbG9zcyA9ICJDcm9zc0VudHJvcHkiLCAjIE9wdGltaXNhdGlvbiBTb2Z0bWF4DQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGlzdHJpYnV0aW9uID0gIm11bHRpbm9taWFsIiwgIyBDbGFzc2lmaWNhdGlvbiBtdWx0aS1jbGFzcw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN0b3BwaW5nX3JvdW5kcyA9IDEwLCAjIEFycsOqdCBhcHLDqHMgMTAgaXTDqXJhdGlvbnMgc2FucyBhbcOpbGlvcmF0aW9uIHNww6ljaWZpcXVlDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3RvcHBpbmdfbWV0cmljID0gIm1pc2NsYXNzaWZpY2F0aW9uIiwgIyBNaW5pbWlzYXRpb24gZGUgbCdlcnJldXINCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdG9wcGluZ190b2xlcmFuY2UgPSAwLjAwMDAxLCAjIFRvbMOpcmFuY2UgbWF4aW1hbGUgZGUgMC4wMDElIGRlIHN0YWduYXRpb24gZGUgbCdlcnJldXINCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZXByb2R1Y2libGUgPSBUUlVFLCAjIFRlbnRhdGl2ZSBkZSByw6lzdWx0YXRzIHJlcHJvZHVjdGlibGVzDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2VlZCA9IDApKSAjIFJlcHJvZHVjdGlvbiBkZXMgcsOpc3VsdGF0cw0KICANCn0NCg0KIyBCb3VjbGUgZCfDqXZhbHVhdGlvbg0KZm9yIChpIGluIDE6MTIpIHsNCiAgDQogICMgRW50cmFpbmVtZW50IGR1IG1vZMOobGUgZGUgcsOpZ3Jlc3Npb24gbG9naXN0aXF1ZSAoeGdib29zdCkNCiAgdGVtcF9tb2RlbCA8LSB4Z2JfZHluYW1pY190cmFpbih0cmFpbiA9IHRyYWluaW5nX3hnYltbaV1dLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRlc3QgPSB0ZXN0aW5nX3hnYltbaV1dLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJvb3N0ZXIgPSAiZ2JsaW5lYXIiLCAjIExpbsOpYWlyZQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5yb3VuZHMgPSAxMDAwMDAwLCAjIEFycsOqdMOpIGF1IG1laWxsZXVyIHLDqXN1bHRhdA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG51bV9wYXJhbGxlbF90cmVlcyA9IDEpDQogIHhnYi5kdW1wKG1vZGVsID0gdGVtcF9tb2RlbCwgIyBNb2TDqGxlIMOgIGVucmVnaXN0cmVyDQogICAgICAgICAgIGZuYW1lID0gcGFzdGUwKGZpbGVfdGFnLCAieGdiX2dsbV8iLCBzcHJpbnRmKCIlMDJkIiwgaSksICIuanNvbiIpLCAjIE/DuSBlbnJlZ2lzdHJlciBsZSBtb2TDqGxlID8NCiAgICAgICAgICAgd2l0aF9zdGF0cyA9IFRSVUUsICMgRW5yZWdpc3RyZW1lbnQgZGVzIHN0YXRpc3RpcXVlcyBzaSBtb2TDqGxlIGdidHJlZQ0KICAgICAgICAgICBkdW1wX2Zvcm1hdCA9ICJqc29uIikgIyBEdW1wIGF1IGZvcm1hdCBqc29uLCByw6ktdXRpbGlzYWJsZQ0KICBhY2N1cmFjeVtpLCAyXSA8LSAxIC0gdGVtcF9tb2RlbCRldmFsdWF0aW9uX2xvZ1tbMl1dW3RlbXBfbW9kZWwkYmVzdF9pdGVyYXRpb25dICMgUsOpY3Vww6lyYXRpb24gZHUgbWVpbGxldXIgcsOpc3VsdGF0DQogIA0KICAjIEVudHJhaW5lbWVudCBkdSBtb2TDqGxlIGQnYXJicmUgZGUgZMOpY2lzaW9uICh4Z2Jvb3N0KQ0KICB0ZW1wX21vZGVsIDwtIHhnYl9keW5hbWljX3RyYWluKHRyYWluID0gdHJhaW5pbmdfeGdiW1tpXV0sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGVzdCA9IHRlc3RpbmdfeGdiW1tpXV0sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYm9vc3RlciA9ICJnYnRyZWUiLCAjIE5vbi1saW7DqWFpcmUNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBucm91bmRzID0gMSwgIyBVbiBzZXVsIGFyYnJlDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbnVtX3BhcmFsbGVsX3RyZWVzID0gMSkNCiAgeGdiLmR1bXAobW9kZWwgPSB0ZW1wX21vZGVsLCAjIE1vZMOobGUgw6AgZW5yZWdpc3RyZXINCiAgICAgICAgICAgZm5hbWUgPSBwYXN0ZTAoZmlsZV90YWcsICJ4Z2JfZHRfIiwgc3ByaW50ZigiJTAyZCIsIGkpLCAiLmpzb24iKSwgIyBPw7kgZW5yZWdpc3RyZXIgbGUgbW9kw6hsZSA/DQogICAgICAgICAgIHdpdGhfc3RhdHMgPSBUUlVFLCAjIEVucmVnaXN0cmVtZW50IGRlcyBzdGF0aXN0aXF1ZXMgc2kgbW9kw6hsZSBnYnRyZWUNCiAgICAgICAgICAgZHVtcF9mb3JtYXQgPSAianNvbiIpICMgRHVtcCBhdSBmb3JtYXQganNvbiwgcsOpLXV0aWxpc2FibGUNCiAgYWNjdXJhY3lbaSwgM10gPC0gMSAtIHRlbXBfbW9kZWwkZXZhbHVhdGlvbl9sb2dbWzJdXVsxXSAjIFLDqWN1cMOpcmF0aW9uIGR1IG1laWxsZXVyIHLDqXN1bHRhdA0KICANCiAgIyBFbnRyYWluZW1lbnQgZHUgbW9kw6hsZSBkZSBSYW5kb20gRm9yZXN0ICh4Z2Jvb3N0KQ0KICB0ZW1wX21vZGVsIDwtIHhnYl9keW5hbWljX3RyYWluKHRyYWluID0gdHJhaW5pbmdfeGdiW1tpXV0sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGVzdCA9IHRlc3RpbmdfeGdiW1tpXV0sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYm9vc3RlciA9ICJnYnRyZWUiLCAjIE5vbi1saW7DqWFpcmUNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBucm91bmRzID0gMSwgIyBVbmUgc2V1bGUgaXTDqXJhdGlvbg0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG51bV9wYXJhbGxlbF90cmVlcyA9IDIwMCkgIyBEZSAyMDAgYXJicmVzDQogIHhnYi5kdW1wKG1vZGVsID0gdGVtcF9tb2RlbCwgIyBNb2TDqGxlIMOgIGVucmVnaXN0cmVyDQogICAgICAgICAgIGZuYW1lID0gcGFzdGUwKGZpbGVfdGFnLCAieGdiX3JmXyIsIHNwcmludGYoIiUwMmQiLCBpKSwgIi5qc29uIiksICMgT8O5IGVucmVnaXN0cmVyIGxlIG1vZMOobGUgPw0KICAgICAgICAgICB3aXRoX3N0YXRzID0gVFJVRSwgIyBFbnJlZ2lzdHJlbWVudCBkZXMgc3RhdGlzdGlxdWVzIHNpIG1vZMOobGUgZ2J0cmVlDQogICAgICAgICAgIGR1bXBfZm9ybWF0ID0gImpzb24iKSAjIER1bXAgYXUgZm9ybWF0IGpzb24sIHLDqS11dGlsaXNhYmxlDQogIGFjY3VyYWN5W2ksIDRdIDwtIDEgLSB0ZW1wX21vZGVsJGV2YWx1YXRpb25fbG9nW1syXV0gIyBSw6ljdXDDqXJhdGlvbiBkdSBtZWlsbGV1ciByw6lzdWx0YXQNCiAgDQogICMgRW50cmFpbmVtZW50IGR1IG1vZMOobGUgZCdhcmJyZSBkZSBkw6ljaXNpb24gYm9vc3TDqSBhdmVjIHByb3RlY3Rpb24gY29udHJlIGwnb3ZlcmZpdHRpbmcgKHhnYm9vc3QpDQogIHRlbXBfbW9kZWwgPC0geGdiX2R5bmFtaWNfdHJhaW4odHJhaW4gPSB0cmFpbmluZ194Z2JbW2ldXSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0ZXN0ID0gdGVzdGluZ194Z2JbW2ldXSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBib29zdGVyID0gImdidHJlZSIsICMgTm9uLWxpbsOpYWlyZQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5yb3VuZHMgPSAxMDAwMDAwLCAjIEFycsOqdMOpIGF1IG1laWxsZXVyIHLDqXN1bHRhdA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG51bV9wYXJhbGxlbF90cmVlcyA9IDEpDQogIHhnYi5kdW1wKG1vZGVsID0gdGVtcF9tb2RlbCwgIyBNb2TDqGxlIMOgIGVucmVnaXN0cmVyDQogICAgICAgICAgIGZuYW1lID0gcGFzdGUwKGZpbGVfdGFnLCAieGdiX2didF8iLCBzcHJpbnRmKCIlMDJkIiwgaSksICIuanNvbiIpLCAjIE/DuSBlbnJlZ2lzdHJlciBsZSBtb2TDqGxlID8NCiAgICAgICAgICAgd2l0aF9zdGF0cyA9IFRSVUUsICMgRW5yZWdpc3RyZW1lbnQgZGVzIHN0YXRpc3RpcXVlcyBzaSBtb2TDqGxlIGdidHJlZQ0KICAgICAgICAgICBkdW1wX2Zvcm1hdCA9ICJqc29uIikgIyBEdW1wIGF1IGZvcm1hdCBqc29uLCByw6ktdXRpbGlzYWJsZQ0KICBhY2N1cmFjeVtpLCA1XSA8LSAxIC0gdGVtcF9tb2RlbCRldmFsdWF0aW9uX2xvZ1tbMl1dW3RlbXBfbW9kZWwkYmVzdF9pdGVyYXRpb25dICMgUsOpY3Vww6lyYXRpb24gZHUgbWVpbGxldXIgcsOpc3VsdGF0DQogIA0KICAjIEVudHJhaW5lbWVudCBkdSBtb2TDqGxlIGRlIHLDqWdyZXNzaW9uIGxvZ2lzdGlxdWUgKGgybykNCiAgdGVtcF9tb2RlbCA8LSBoMm8uZ2xtKHkgPSAxLA0KICAgICAgICAgICAgICAgICAgICAgICAgdHJhaW5pbmdfZnJhbWUgPSB0cmFpbmluZ19oMm9bW2ldXSwNCiAgICAgICAgICAgICAgICAgICAgICAgIHZhbGlkYXRpb25fZnJhbWUgPSB0ZXN0aW5nX2gyb1tbaV1dLA0KICAgICAgICAgICAgICAgICAgICAgICAgbW9kZWxfaWQgPSBwYXN0ZTAoImgyb19nbG1fIiwgc3ByaW50ZigiJTAyZCIsIGkpKSwgIyBOb20gZHUgbW9kw6hsZQ0KICAgICAgICAgICAgICAgICAgICAgICAgbWF4X2l0ZXJhdGlvbnMgPSAxMDAsICMgMTAwIGl0w6lyYXRpb25zIGQnb3B0aW1pc2F0aW9uDQogICAgICAgICAgICAgICAgICAgICAgICBzb2x2ZXIgPSAiSVJMU00iLCAjIFNvbHZldXIgcGFyIGTDqWZhdXQNCiAgICAgICAgICAgICAgICAgICAgICAgIHN0YW5kYXJkaXplID0gRkFMU0UsICMgUGFzIGRlIHN0YW5kYXJkaXNhdGlvbiBwdWlzcXVlIFstMSwgMV0NCiAgICAgICAgICAgICAgICAgICAgICAgIGZhbWlseSA9ICJtdWx0aW5vbWlhbCIsICMgQ2xhc3NpZmljYXRpb24gbXVsdGktY2xhc3NlDQogICAgICAgICAgICAgICAgICAgICAgICBzZWVkID0gMCwgIyBSZXByb2R1Y3Rpb24gZGVzIHLDqXN1bHRhdHMNCiAgICAgICAgICAgICAgICAgICAgICAgIGludGVyY2VwdCA9IFRSVUUpDQogIGgyby5kb3dubG9hZF9wb2pvKHRlbXBfbW9kZWwsICMgTW9kw6hsZSDDoCBlbnJlZ2lzdHJlcg0KICAgICAgICAgICAgICAgICAgICBwYXRoID0gZmlsZV9oMm8sICMgT8O5IGVucmVnaXN0cmVyIGxlIG1vZMOobGUgPw0KICAgICAgICAgICAgICAgICAgICBnZXRfamFyID0gRkFMU0UpICMgUGFzIGRlIGZpY2hpZXIgLmphcg0KICBhY2N1cmFjeVtpLCA2XSA8LSB0ZW1wX21vZGVsQG1vZGVsJHZhbGlkYXRpb25fbWV0cmljc0BtZXRyaWNzJGhpdF9yYXRpb190YWJsZVsxLCAyXQ0KICANCiAgIyBFbnRyYWluZW1lbnQgZHUgbW9kw6hsZSBkJ2FyYnJlIGRlIGTDqWNpc2lvbiAoaDJvKQ0KICB0ZW1wX21vZGVsIDwtIGgyby5yYW5kb21Gb3Jlc3QoeSA9IDEsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0cmFpbmluZ19mcmFtZSA9IHRyYWluaW5nX2gyb1tbaV1dLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdmFsaWRhdGlvbl9mcmFtZSA9IHRlc3RpbmdfaDJvW1tpXV0sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtb2RlbF9pZCA9IHBhc3RlMCgiaDJvX2R0XyIsIHNwcmludGYoIiUwMmQiLCBpKSksICMgTm9tIGR1IG1vZMOobGUNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNhbXBsZV9yYXRlID0gMSwgIyBUb3V0ZXMgbGVzIG9ic2VydmF0aW9ucyBzZXJvbnQgcHJpc2VzIGVuIGNvbXB0ZSBwb3VyIGxlIHNldWwgYXJicmUgZGUgZMOpY2lzaW9uDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtdHJpZXMgPSAzNiwgIyBUb3V0ZXMgbGVzIGZlYXR1cmVzIHNlcm9udCBwcmlzZXMgZW4gY29tcHRlIHBvdXIgbGUgc2V1bCBhcmJyZSBkZSBkw6ljaXNpb24NCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG50cmVlcyA9IDEsICMgVW4gc2V1bCBhcmJyZQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2VlZCA9IDApICMgUmVwcm9kdWN0aW9uIGRlcyByw6lzdWx0YXRzDQogIGgyby5kb3dubG9hZF9wb2pvKHRlbXBfbW9kZWwsICMgTW9kw6hsZSDDoCBlbnJlZ2lzdHJlcg0KICAgICAgICAgICAgICAgICAgICBwYXRoID0gZmlsZV9oMm8sICMgT8O5IGVucmVnaXN0cmVyIGxlIG1vZMOobGUgPw0KICAgICAgICAgICAgICAgICAgICBnZXRfamFyID0gRkFMU0UpICMgUGFzIGRlIGZpY2hpZXIgLmphcg0KICBhY2N1cmFjeVtpLCA3XSA8LSAxIC0gbWluKHRlbXBfbW9kZWxAbW9kZWwkc2NvcmluZ19oaXN0b3J5JHZhbGlkYXRpb25fY2xhc3NpZmljYXRpb25fZXJyb3IsIG5hLnJtID0gVFJVRSkNCiAgDQogICMgRW50cmFpbmVtZW50IGR1IG1vZMOobGUgZGUgUmFuZG9tIEZvcmVzdCAoaDJvKQ0KICB0ZW1wX21vZGVsIDwtIGgyby5yYW5kb21Gb3Jlc3QoeSA9IDEsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0cmFpbmluZ19mcmFtZSA9IHRyYWluaW5nX2gyb1tbaV1dLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdmFsaWRhdGlvbl9mcmFtZSA9IHRlc3RpbmdfaDJvW1tpXV0sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtb2RlbF9pZCA9IHBhc3RlMCgiaDJvX3JmXyIsIHNwcmludGYoIiUwMmQiLCBpKSksICMgTm9tIGR1IG1vZMOobGUNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNhbXBsZV9yYXRlID0gMC42MzIsICMgQm9vdHN0cmFwcGluZyAuNjMyIHBvdXIgY2hhcXVlIGFyYnJlIGRlIGTDqWNpc2lvbg0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbXRyaWVzID0gLTEsICMgc3FydCgzNikgZmVhdHVyZXMgc2Vyb250IHByaXNlcyBlbiBjb21wdGUgcG91ciBjaGFxdWUgYXJicmUgZGUgZMOpY2lzaW9uDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBudHJlZXMgPSAyMDAsICMgMjAwIGFyYnJlcw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2VlZCA9IDApICMgUmVwcm9kdWN0aW9uIGRlcyByw6lzdWx0YXRzDQogIGgyby5kb3dubG9hZF9wb2pvKHRlbXBfbW9kZWwsICMgTW9kw6hsZSDDoCBlbnJlZ2lzdHJlcg0KICAgICAgICAgICAgICAgICAgICBwYXRoID0gZmlsZV9oMm8sICMgT8O5IGVucmVnaXN0cmVyIGxlIG1vZMOobGUgPw0KICAgICAgICAgICAgICAgICAgICBnZXRfamFyID0gRkFMU0UpICMgUGFzIGRlIGZpY2hpZXIgLmphcg0KICBhY2N1cmFjeVtpLCA4XSA8LSAxIC0gbWluKHRlbXBfbW9kZWxAbW9kZWwkc2NvcmluZ19oaXN0b3J5JHZhbGlkYXRpb25fY2xhc3NpZmljYXRpb25fZXJyb3IsIG5hLnJtID0gVFJVRSkNCiAgDQogICMgRW50cmFpbmVtZW50IGR1IG1vZMOobGUgZCdhcmJyZSBkZSBkw6ljaXNpb24gYm9vc3TDqSBhdmVjIHByb3RlY3Rpb24gY29udHJlIGwnb3ZlcmZpdHRpbmcgKGgybykNCiAgdGVtcF9tb2RlbCA8LSBoMm8uZ2JtKHkgPSAxLA0KICAgICAgICAgICAgICAgICAgICAgIHRyYWluaW5nX2ZyYW1lID0gdHJhaW5pbmdfaDJvW1tpXV0sDQogICAgICAgICAgICAgICAgICAgICAgdmFsaWRhdGlvbl9mcmFtZSA9IHRlc3RpbmdfaDJvW1tpXV0sDQogICAgICAgICAgICAgICAgICAgICAgbW9kZWxfaWQgPSBwYXN0ZTAoImgyb19nYnRfIiwgc3ByaW50ZigiJTAyZCIsIGkpKSwgIyBOb20gZHUgbW9kw6hsZQ0KICAgICAgICAgICAgICAgICAgICAgIGRpc3RyaWJ1dGlvbiA9ICJtdWx0aW5vbWlhbCIsICMgQ2xhc3NpZmljYXRpb24gbXVsdGktY2xhc3NlDQogICAgICAgICAgICAgICAgICAgICAgc2FtcGxlX3JhdGUgPSAxLCAjIFBhcyBkZSBwcm9jZXNzdXMgc3RvY2hhc3RpcXVlDQogICAgICAgICAgICAgICAgICAgICAgbnRyZWVzID0gMTAwLCAjIDEwMCBpdMOpcmF0aW9ucyBkZSBib29zdGluZyBhdSBtYXhpbXVtDQogICAgICAgICAgICAgICAgICAgICAgc2NvcmVfZWFjaF9pdGVyYXRpb24gPSBUUlVFLCAjIE5vdGVyIGxhIHZhbGV1ciBkZSBjaGFxdWUgaXTDqXJhdGlvbg0KICAgICAgICAgICAgICAgICAgICAgIHN0b3BwaW5nX3JvdW5kcyA9IDEwLCAjIEFycsOqdCBhcHLDqHMgMTAgaXTDqXJhdGlvbnMgc2FucyBhbcOpbGlvcmF0b24gZGUgbGEgbcOpdHJpcXVlDQogICAgICAgICAgICAgICAgICAgICAgc3RvcHBpbmdfbWV0cmljID0gIm1pc2NsYXNzaWZpY2F0aW9uIiwgIyBTdXJ2ZWlsbGVyIGwnaW5leGFjdGl0dWRlIGRlIGxhIGNsYXNzaWZpY2F0aW9uIHBvdXIgbCdhcnLDqnQNCiAgICAgICAgICAgICAgICAgICAgICBzdG9wcGluZ190b2xlcmFuY2UgPSAwLjAwMDAxLCAjIEFycsOqdGVyIGxvcnNxdWUgbGEgbcOpdHJpcXVlIHN0YWduZSBkZSAwLjAwMSUNCiAgICAgICAgICAgICAgICAgICAgICBzZWVkID0gMCkgIyBSZXByb2R1Y3Rpb24gZGVzIHLDqXN1bHRhdHMNCiAgaDJvLmRvd25sb2FkX3Bvam8odGVtcF9tb2RlbCwgIyBNb2TDqGxlIMOgIGVucmVnaXN0cmVyDQogICAgICAgICAgICAgICAgICAgIHBhdGggPSBmaWxlX2gybywgIyBPw7kgZW5yZWdpc3RyZXIgbGUgbW9kw6hsZSA/DQogICAgICAgICAgICAgICAgICAgIGdldF9qYXIgPSBGQUxTRSkgIyBQYXMgZGUgZmljaGllciAuamFyDQogIGFjY3VyYWN5W2ksIDldIDwtIDEgLSBtaW4odGVtcF9tb2RlbEBtb2RlbCRzY29yaW5nX2hpc3RvcnkkdmFsaWRhdGlvbl9jbGFzc2lmaWNhdGlvbl9lcnJvciwgbmEucm0gPSBUUlVFKQ0KICANCiAgIyBFbnRyYWluZW1lbnQgZHUgcsOpc2VhdSBkZSBuZXVyb25lcyDDoCBhcmNoaXRlY3R1cmUgMzJ4NiArIFJlTFUgKGgybykNCiAgdGVtcF9tb2RlbCA8LSBoMm9fbm5fdHJhaW4odHJhaW4gPSB0cmFpbmluZ19oMm9bW2ldXSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGVzdCA9IHRlc3RpbmdfaDJvW1tpXV0sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1vZGVsX2lkID0gcGFzdGUwKCJoMm9fbm5fMzJ4Nl9SZUxVXyIsIHNwcmludGYoIiUwMmQiLCBpKSksICMgTm9tIGR1IG1vZMOobGUNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYWN0aXZhdGlvbiA9ICJSZWN0aWZpZXIiLCAjIFJlTFUNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaGlkZGVuID0gMzIpICMgQXJjaGl0ZWN0dXJlIDMyeDYNCiAgaDJvLmRvd25sb2FkX3Bvam8odGVtcF9tb2RlbCwgIyBNb2TDqGxlIMOgIGVucmVnaXN0cmVyDQogICAgICAgICAgICAgICAgICAgIHBhdGggPSBmaWxlX2gybywgIyBPw7kgZW5yZWdpc3RyZXIgbGUgbW9kw6hsZSA/DQogICAgICAgICAgICAgICAgICAgIGdldF9qYXIgPSBGQUxTRSkgIyBQYXMgZGUgZmljaGllciAuamFyDQogIGFjY3VyYWN5W2ksIDEwXSA8LSAxIC0gbWluKHRlbXBfbW9kZWxAbW9kZWwkc2NvcmluZ19oaXN0b3J5JHZhbGlkYXRpb25fY2xhc3NpZmljYXRpb25fZXJyb3IsIG5hLnJtID0gVFJVRSkNCiAgDQogICMgRW50cmFpbmVtZW50IGR1IHLDqXNlYXUgZGUgbmV1cm9uZXMgw6AgYXJjaGl0ZWN0dXJlIDMyeDYgKyBUYW5oIChoMm8pDQogIHRlbXBfbW9kZWwgPC0gaDJvX25uX3RyYWluKHRyYWluID0gdHJhaW5pbmdfaDJvW1tpXV0sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRlc3QgPSB0ZXN0aW5nX2gyb1tbaV1dLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtb2RlbF9pZCA9IHBhc3RlMCgiaDJvX25uXzMyeDZfVGFuaF8iLCBzcHJpbnRmKCIlMDJkIiwgaSkpLCAjIE5vbSBkdSBtb2TDqGxlDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFjdGl2YXRpb24gPSAiVGFuaCIsICMgIlNpZ21vaWRlIg0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBoaWRkZW4gPSAzMikgIyBBcmNoaXRlY3R1cmUgMzJ4Ng0KICBoMm8uZG93bmxvYWRfcG9qbyh0ZW1wX21vZGVsLCAjIE1vZMOobGUgw6AgZW5yZWdpc3RyZXINCiAgICAgICAgICAgICAgICAgICAgcGF0aCA9IGZpbGVfaDJvLCAjIE/DuSBlbnJlZ2lzdHJlciBsZSBtb2TDqGxlID8NCiAgICAgICAgICAgICAgICAgICAgZ2V0X2phciA9IEZBTFNFKSAjIFBhcyBkZSBmaWNoaWVyIC5qYXINCiAgYWNjdXJhY3lbaSwgMTFdIDwtIDEgLSBtaW4odGVtcF9tb2RlbEBtb2RlbCRzY29yaW5nX2hpc3RvcnkkdmFsaWRhdGlvbl9jbGFzc2lmaWNhdGlvbl9lcnJvciwgbmEucm0gPSBUUlVFKQ0KICANCiAgIyBFbnRyYWluZW1lbnQgZHUgcsOpc2VhdSBkZSBuZXVyb25lcyDDoCBhcmNoaXRlY3R1cmUgMTZ4MTZ4NiArIFJlTFUgKGgybykNCiAgdGVtcF9tb2RlbCA8LSBoMm9fbm5fdHJhaW4odHJhaW4gPSB0cmFpbmluZ19oMm9bW2ldXSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGVzdCA9IHRlc3RpbmdfaDJvW1tpXV0sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1vZGVsX2lkID0gcGFzdGUwKCJoMm9fbm5fMTZ4MTZ4Nl9SZUxVXyIsIHNwcmludGYoIiUwMmQiLCBpKSksICMgTm9tIGR1IG1vZMOobGUNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYWN0aXZhdGlvbiA9ICJSZWN0aWZpZXIiLCAjIFJlTFUNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaGlkZGVuID0gYygxNiwgMTYpKSAjIEFyY2hpdGVjdHVyZSAxNngxNng2DQogIGgyby5kb3dubG9hZF9wb2pvKHRlbXBfbW9kZWwsICMgTW9kw6hsZSDDoCBlbnJlZ2lzdHJlcg0KICAgICAgICAgICAgICAgICAgICBwYXRoID0gZmlsZV9oMm8sICMgT8O5IGVucmVnaXN0cmVyIGxlIG1vZMOobGUgPw0KICAgICAgICAgICAgICAgICAgICBnZXRfamFyID0gRkFMU0UpICMgUGFzIGRlIGZpY2hpZXIgLmphcg0KICBhY2N1cmFjeVtpLCAxMl0gPC0gMSAtIG1pbih0ZW1wX21vZGVsQG1vZGVsJHNjb3JpbmdfaGlzdG9yeSR2YWxpZGF0aW9uX2NsYXNzaWZpY2F0aW9uX2Vycm9yLCBuYS5ybSA9IFRSVUUpDQogIA0KICAjIEVudHJhaW5lbWVudCBkdSByw6lzZWF1IGRlIG5ldXJvbmVzIMOgIGFyY2hpdGVjdHVyZSAxNngxNng2ICsgVGFuaCAoaDJvKQ0KICB0ZW1wX21vZGVsIDwtIGgyb19ubl90cmFpbih0cmFpbiA9IHRyYWluaW5nX2gyb1tbaV1dLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0ZXN0ID0gdGVzdGluZ19oMm9bW2ldXSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbW9kZWxfaWQgPSBwYXN0ZTAoImgyb19ubl8xNngxNng2X1RhbmhfIiwgc3ByaW50ZigiJTAyZCIsIGkpKSwgIyBOb20gZHUgbW9kw6hsZQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhY3RpdmF0aW9uID0gIlRhbmgiLCAjICJTaWdtb2lkZSINCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaGlkZGVuID0gYygxNiwgMTYpKSAjIEFyY2hpdGVjdHVyZSAxNngxNng2DQogIGgyby5kb3dubG9hZF9wb2pvKHRlbXBfbW9kZWwsICMgTW9kw6hsZSDDoCBlbnJlZ2lzdHJlcg0KICAgICAgICAgICAgICAgICAgICBwYXRoID0gZmlsZV9oMm8sICMgT8O5IGVucmVnaXN0cmVyIGxlIG1vZMOobGUgPw0KICAgICAgICAgICAgICAgICAgICBnZXRfamFyID0gRkFMU0UpICMgUGFzIGRlIGZpY2hpZXIgLmphcg0KICBhY2N1cmFjeVtpLCAxM10gPC0gMSAtIG1pbih0ZW1wX21vZGVsQG1vZGVsJHNjb3JpbmdfaGlzdG9yeSR2YWxpZGF0aW9uX2NsYXNzaWZpY2F0aW9uX2Vycm9yLCBuYS5ybSA9IFRSVUUpDQogIA0KfQ0KDQojIFRlbXBzIG7DqWNlc3NhaXJlDQp0aW1pbmcoQ3VycmVudFRpbWUsICJDcsOpYXRpb24gZXQgw6l2YWx1YXRpb24gZGVzIGRvdXplIG1vZMOobGVzIGRlIGJlbmNobWFyayIpDQpgYGANCg0KIyMgQW5hbHlzZSBkZXMgcsOpc3VsdGF0cyBwb3VyIGxlcyBkb3V6ZSBtb2TDqGxlcw0KDQpOb3VzIHJlbWFycXVvbnMgY2xhaXJlbWVudCBsZSBwcm9ibMOobWUgcXUnb24gYSBzb3VsZXbDqSBkw6lzIGxlIGTDqWJ1dCA6IGxhIHNhbGxlIDEgcG9zc8OoZGUgZGVzIGZlYXR1cmVzIHF1aSBuZSBzb250IHBhcyBhZMOpcXVhdGVzIGVuIGwnw6l0YXQsIGxlcyBkb25uw6llcyBzZW1ibGUgYmllbiBpbnV0aWxpc2FibGVzIHNhbnMgdHJhbnNmb3JtYXRpb24gcHLDqWFsYWJsZS4NCg0KRGUgZmFpdCwgbGVzIHLDqXN1bHRhdHMgbmUgc29udCBwYXMgZW5jb3JlIGV4cGxvaXRhYmxlcyBkZSBtYW5pw6hyZSBjb3JyZWN0ZS4NCg0KYGBge3IgUmVzdWx0YXRzMX0NCiMgTW95ZW5uZSBkZXMgcsOpc3VsdGF0cw0KZm9yIChpIGluIDI6MTMpIHsNCiAgYWNjdXJhY3lbMTMsIGldIDwtIG1lYW4oYWNjdXJhY3lbMTo2LCBpXSkNCiAgYWNjdXJhY3lbMTQsIGldIDwtIG1lYW4oYWNjdXJhY3lbNzo5LCBpXSkNCiAgYWNjdXJhY3lbMTUsIGldIDwtIG1lYW4oYWNjdXJhY3lbMTA6MTIsIGldKQ0KICBhY2N1cmFjeVsxNiwgaV0gPC0gbWVhbihhY2N1cmFjeVsxMzoxNSwgaV0pDQp9DQoNCiMgRW5yZWdpc3RyZW1lbnQgZGVzIHNjb3Jlcw0KZndyaXRlKGFjY3VyYWN5LCAic2NvcmVzLzFfbW9kZWxzLmNzdiIpDQoNCiMgQWZmaWNoYWdlIGRlcyByw6lzdWx0YXRzIGRhbnMgdW4gdGFibGVhdSBpbnRlcmFjdGlmDQp0b19wcmludCA8LSBkYXRhLnRhYmxlKHQoYWNjdXJhY3lbMTM6MTYsIC0xXSkpICMgUHLDqXBhcmF0aW9uIGRlcyBkb25uw6llcyDDoCBtZXR0cmUgc3VyIHRhYmxlDQpjb2xuYW1lcyh0b19wcmludCkgPC0gYygiMSBjb250cmUgMSIsICIxIGNvbnRyZSAyIiwgIjIgY29udHJlIDEiLCAiTW95ZW5uZSIpICMgUmVtaXNlIGRlcyBub21zIGRlcyBjb2xvbm5lcw0Kcm93Lm5hbWVzKHRvX3ByaW50KSA8LSBjb2xuYW1lcyhhY2N1cmFjeSlbLTFdICMgUmVtaXNlIGRlcyBub21zIGRlcyBsaWduZXMNCmRhdGF0YWJsZSh0b19wcmludCwNCiAgICAgICAgICBmaWx0ZXIgPSAidG9wIiwgIyBGaWx0cmFnZSBhdS1kZXNzdXMgZGUgbGEgdGFibGUNCiAgICAgICAgICBjbGFzcyA9ICJjZWxsLWJvcmRlciBzdHJpcGUiLCAjIENTUw0KICAgICAgICAgIGV4dGVuc2lvbnMgPSBjKCJDb2xSZW9yZGVyIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAiUm93UmVvcmRlciIpLCAjIFJlb3Jkb25uZXIgbWFudWVsbGVtZW50IMOgIGxhIG1haW4NCiAgICAgICAgICBvcHRpb25zID0gbGlzdChwYWdlTGVuZ3RoID0gMTIsICMgUGFnZSBhZmZpY2hhbnQgMTIgbGlnbmVzDQogICAgICAgICAgICAgICAgICAgICAgICAgb3JkZXIgPSBsaXN0KGxpc3QoNCwgImRlc2MiKSksICMgT3Jkb25uZXIgcGFyIGTDqWZhdXQgcGFyIGwnZXhhY3RpdHVkZSBtb3llbm5lDQogICAgICAgICAgICAgICAgICAgICAgICAgY29sUmVvcmRlciA9IFRSVUUsICMgUGx1Z2luDQogICAgICAgICAgICAgICAgICAgICAgICAgcm93UmVvcmRlciA9IFRSVUUpKSAlPiUgIyBQbHVnaW4NCiAgZm9ybWF0U3R5bGUoYygiMSBjb250cmUgMSIsICIxIGNvbnRyZSAyIiwgIjIgY29udHJlIDEiKSwNCiAgICAgICAgICAgICAgICAgIGJhY2tncm91bmQgPSBzdHlsZUNvbG9yQmFyKGMoMCwgMSksICdsaWdodGdyZWVuJyksICMgQ291bGV1ciB2ZXJ0IGNsYWlyIHBvdXIgbGVzIG3DqXRyaXF1ZXMgcGFyIGZvbGQNCiAgICAgICAgICAgICAgICAgIGJhY2tncm91bmRTaXplID0gJzEwMCUgOTAlJywNCiAgICAgICAgICAgICAgICAgIGJhY2tncm91bmRSZXBlYXQgPSAnbm8tcmVwZWF0JywNCiAgICAgICAgICAgICAgICAgIGJhY2tncm91bmRQb3NpdGlvbiA9ICdjZW50ZXInKSAlPiUNCiAgZm9ybWF0U3R5bGUoIk1veWVubmUiLA0KICAgICAgICAgICAgICBiYWNrZ3JvdW5kID0gc3R5bGVDb2xvckJhcihjKDAsIDEpLCAncGluaycpLCAjIENvdWxldXIgcm9zZSBwb3VyIGxhIG3DqXRyaXF1ZSBkZSBtb3llbm5lDQogICAgICAgICAgICAgIGJhY2tncm91bmRTaXplID0gJzEwMCUgOTAlJywNCiAgICAgICAgICAgICAgYmFja2dyb3VuZFJlcGVhdCA9ICduby1yZXBlYXQnLA0KICAgICAgICAgICAgICBiYWNrZ3JvdW5kUG9zaXRpb24gPSAnY2VudGVyJykgJT4lDQogIGZvcm1hdFBlcmNlbnRhZ2UoY29sdW1ucyA9IGMoIjEgY29udHJlIDEiLCAiMSBjb250cmUgMiIsICIyIGNvbnRyZSAxIiksDQogICAgICAgICAgICAgIGRpZ2l0cyA9IDgpICU+JQ0KICBmb3JtYXRQZXJjZW50YWdlKGNvbHVtbnMgPSAiTW95ZW5uZSIsDQogICAgICAgICAgICAgIGRpZ2l0cyA9IDgpDQoNCiMgQWZmaWNoYWdlIGRlcyByw6lzdWx0YXRzIGRhbnMgdW4gdGFibGVhdSBzdGF0aXF1ZQ0KZm9ybWF0dGFibGUoYWNjdXJhY3lbLCBjKDEsIDI6NSldLCBsaXN0KGZvcm1hdHRhYmxlOjphcmVhKGNvbCA9IHhnYl9MaW5lYXJNb2RlbDp4Z2JfR3JhZGllbnRCb29zdGluZykgfiBjb2xvcl9iYXIoIm9yYW5nZSIpKSkNCmZvcm1hdHRhYmxlKGFjY3VyYWN5WywgYygxLCA2OjkpXSwgbGlzdChmb3JtYXR0YWJsZTo6YXJlYShjb2wgPSBoMm9fTGluZWFyTW9kZWw6aDJvX0dyYWRpZW50Qm9vc3RpbmcpIH4gY29sb3JfYmFyKCJjeWFuIikpKQ0KZm9ybWF0dGFibGUoYWNjdXJhY3lbLCBjKDEsIDEwOjEzKV0sIGxpc3QoZm9ybWF0dGFibGU6OmFyZWEoY29sID0gaDJvX05OXzMyeDZfUmVMVTpoMm9fTk5fMTZ4MTZ4Nl9Tb2Z0KSB+IGNvbG9yX2JhcigieWVsbG93IikpKQ0KYGBgDQoNCiMgU2Vjb25kZSBBbmFseXNlIEV4cGxvcmF0b2lyZQ0KDQpJbCBmYXVkcmEgcHLDqXBhcmVyIGNvcnJlY3RlbWVudCBsZXMgZG9ubsOpZXMuIFBvdXIgY2VsYSwgZGV1eCDDqXRhcGVzIDoNCg0KLSBOZXR0b3llciBsZXMgZG9ubsOpZXMNCi0gQ3LDqWVyIGxlcyBmZWF0dXJlcyBkZSBub3V2ZWF1DQoNCkVuc3VpdGUsIG9uIHBvdXJyYSByZWZhaXJlIGwnYW5hbHlzZSBzeXN0w6ltaXF1ZS4NCg0KIyMgTmV0dG95YWdlIGRlcyBkb25uw6llcw0KDQpPbiBwZXV0IHRlbnRlciBkZSB0cmF2YWlsbGVyIHN1ciBsYSByw6lzb2x1dGlvbiBkdSBwcm9ibMOobWUgZGVzIHNpZ25hdXggbWl4w6lzLCBhaW5zaSBxdWUgbGUgcHJvYmzDqG1lIGRlIGdhaW4uDQoNClN1cHBvc29ucyBxdWUgbGEgU2FsbGUgMiBhIGJpZW4gw6l0w6kgcGFpcsOpZSBhdmVjIGxhIFNhbGxlIDEsIGFsb3JzIDoNCg0KLSBMZXMgc2lnbmF1eCBtaXjDqXMgZGV2cmFpZW50IGF2b2lyIGxldXIgc2lnbmUgaW52ZXJzw6kgKG1pcyDDoCBwYXJ0IGxlcyBmbHVjdHVhdGlvbnMgZGUgZ2FpbikNCi0gTGVzIHNpZ25hdXggZmFpYmxlcyBkZXZyYWllbnQgYXZvaXIgbGV1ciBjb2VmZmljaWVudCBtdWx0aXBsaWNhdGV1ciBhdWdtZW50w6kgKG1pcyDDoCBwYXJ0IGxlcyBmbHVjdHVhdGlvbnMgZGUgZ2FpbikNCg0KT24gc2UgcmV0cm91dmUgZG9uYyBhdmVjIHVuIG1vZMOobGUgZGUgdHlwZSBmKHgpID0gYXgrYiwgcXVpIGVzdCB1biBwcm9ibMOobWUgbGluw6lhaXJlIGZhY2lsZW1lbnQgb3B0aW1pc2FibGUuIE5vdXMgYWxsb25zIHV0aWxpc2VyIGxhIG3DqXRob2RlIGRlcyBtb2luZHJlcyBjYXJyw6lzIHBvdXIgYWp1c3RlciBsZXMgdmFsZXVycyBkZSBsYSBTYWxsZSAxLCDDoCBwYXJ0aXIgZGUgY2VsbGVzIGRlIGxhIFNhbGxlIDIuDQoNCkFmaW4gZGUgbmUgcGFzIGludHJvZHVpcmUgZGUgbGVha2FnZSwgbm91cyB0cmF2YWlsbGVyb25zIGF1IG5pdmVhdSBnbG9iYWwgKHNhbnMgcHJlbmRyZSBlbiBjb21wdGUgbGUgbGFiZWwpIGRlcyBzYWxsZXMsIGNlIHF1aSBuJ2VzdCBwYXMgZm9yY8OpbWVudCBsYSBtZWlsbGV1cmUgbcOpdGhvZGUgKG1haXMgZXN0IHBsdXMgc8O7cmUpLiBMZXMgcsOpc3VsdGF0cyBzZW1ibGVudCBiaWVuIG1laWxsZXVycyBkJ2FwcsOocyBsZSBncmFwaGlxdWUuIE5vdXMgcG91cnJvbnMgdGVzdGVyIG5vdHJlIGh5cG90aMOoc2UgZCdhbcOpbGlvcmF0aW9uIGRlcyByw6lzdWx0YXRzIGRhbnMgbGEgcHJvY2hhaW5lIGFuYWx5c2Ugc3lzdMOpbWlxdWUuDQoNCmBgYHtyIE5ldHRveWFnZX0NCiMgQ29tcHRldXIgZGUgdGVtcHMNCkN1cnJlbnRUaW1lIDwtIHRpbWVyKCkgIyBDaHVuayBOZXR0b3lhZ2UgZGVzIGRvbm7DqWVzDQoNCiMgU2F1dmVnYXJkZSBkZXMgYW5jaWVubmVzIGRvbm7DqWVzIHBhciBkZWVwIGNvcHkNCmRhdGFfcHJlX29sZCA8LSBsaXN0KCkNCmZvciAoaSBpbiAxOjMxNCkgew0KICBkYXRhX3ByZV9vbGRbW2ldXSA8LSBjb3B5KGRhdGFfcHJlW1tpXV0pICMgUmVwbGljYXRpb24gZW4gbcOpbW9pcmUgYXUgbGlldSBkZSBsYSBjb3BpZSBkdSBwb2ludGV1cg0KfQ0KDQojIEJvdWNsZSBwYXIgYW5jcmUNCmZvciAoaSBpbiAxOjQpIHsNCiAgDQogICMgUmVncm91cGFnZSBkZXMgZG9ubsOpZXMgc2Vsb24gbCdhbmNyZSBldCBsYSBzYWxsZQ0KICB0ZW1wX3NhbGxlMSA8LSBhdmdfc2VyaWVzWyhBbmNob3IgPT0gcGFzdGUwKCJBbmNyZSIsIGkpKSAmIChSb29tID09ICJTYWxsZTEiKSwgXSRTdHJlbmd0aA0KICB0ZW1wX3NhbGxlMiA8LSBhdmdfc2VyaWVzWyhBbmNob3IgPT0gcGFzdGUwKCJBbmNyZSIsIGkpKSAmIChSb29tID09ICJTYWxsZTIiKSwgXSRTdHJlbmd0aA0KICANCiAgIyBEw6l0ZXJtaW5hdGlvbiBkdSBtb2TDqGxlIGxpbsOpYWlyZSBwYXIgbGEgbcOpdGhvZGUgZGVzIG1vaW5kcmVzIGNhcnLDqXMNCiAgdGVtcF9tb2RlbCA8LSBmYXN0TG1QdXJlKFggPSBjYmluZChhcy5tYXRyaXgodGVtcF9zYWxsZTEpLCByZXAoMSwgbGVuZ3RoKHRlbXBfc2FsbGUxKSkpLCB5ID0gdGVtcF9zYWxsZTIpDQogIA0KICAjIE5ldHRveWFnZSBkZXMgZG9ubsOpZXMgZCdhcHLDqHMgbGUgY29lZmZpY2llbnQgZGlyZWN0ZXVyIGV0IGwnaW50ZXJzZWN0aW9uDQogIGZvciAoaiBpbiB3aGljaChncm91cF9yb29tJGRhdGFzZXRfSUQgPT0gMSkpIHsNCiAgICBkYXRhX3ByZVtbal1dW1tpXV0gPC0gZGF0YV9wcmVbW2pdXVtbaV1dICogdGVtcF9tb2RlbCRjb2VmZmljaWVudHNbMV0gKyB0ZW1wX21vZGVsJGNvZWZmaWNpZW50c1syXQ0KICB9DQogIA0KfQ0KDQojIFJlbGFuY2VtZW50IGRlIGwnYWdyw6lnYXRpb24gZGVzIGRvbm7DqWVzDQphdmdfc2VyaWVzX2NsZWFuIDwtIGRhdGEudGFibGUobWF0cml4KHJlcCgwLCAxNiAqIDQgKiA2ICogNiksIG5yb3cgPSAxNiAqIDQgKiA2LCBuY29sID0gNCkpDQpjb2xuYW1lcyhhdmdfc2VyaWVzX2NsZWFuKSA8LSBjKCJTdHJlbmd0aCIsICJBbmNob3IiLCAiTGFiZWwiLCAiVGltZSIpDQoNCiMgUHLDqS1maWxsaW5nIGRlcyBmYWN0ZXVycyAoQW5jaG9yLCBMYWJlbCwgVGltZSkNCmF2Z19zZXJpZXNfY2xlYW5bWyJBbmNob3IiXV0gPC0gYXMuZmFjdG9yKHJlcChpbnZlcnNlLnJsZShsaXN0KGxlbmd0aHMgPSByZXAoMTYsIDQpLCB2YWx1ZXMgPSAxOjQpKSwgNikpDQpsZXZlbHMoYXZnX3Nlcmllc19jbGVhbltbIkFuY2hvciJdXSkgPC0gcGFzdGUoIkFuY3JlIiwgMTo0LCBzZXAgPSAiIikNCmF2Z19zZXJpZXNfY2xlYW5bWyJMYWJlbCJdXSA8LSBhcy5mYWN0b3IoaW52ZXJzZS5ybGUobGlzdChsZW5ndGhzID0gcmVwKDE2ICogNCwgNiksIHZhbHVlcyA9IDE6NikpKQ0KbGV2ZWxzKGF2Z19zZXJpZXNfY2xlYW5bWyJMYWJlbCJdXSkgPC0gcGFzdGUoIlRyYWplY3RvaXJlIiwgMTo2LCBzZXAgPSAiIikNCmF2Z19zZXJpZXNfY2xlYW5bWyJUaW1lIl1dIDwtIHJlcCgxOjE2LCA2ICogNCkNCg0KIyBUcmFuc2Zvcm1hdGlvbiBlbiBsaXN0ZSBwYXIgc2FsbGUgw6AgZMOpcGl2b3RlciBwYXIgbGEgc3VpdGUNCmF2Z19zZXJpZXNfY2xlYW4gPC0gbGlzdChjYmluZChhdmdfc2VyaWVzX2NsZWFuLCBSb29tID0gcmVwKDEsIDM4NCkpLA0KICAgICAgICAgICAgICAgICAgICAgICAgIGNiaW5kKGF2Z19zZXJpZXNfY2xlYW4sIFJvb20gPSByZXAoMiwgMzg0KSksDQogICAgICAgICAgICAgICAgICAgICAgICAgY2JpbmQoYXZnX3Nlcmllc19jbGVhbiwgUm9vbSA9IHJlcCgzLCAzODQpKSkNCg0KIyBQcsOpLWNvbXB0ZSBkdSBub21icmUgZCdvY2N1cnJlbmNlIGRlcyBsYWJlbHMNCmxhYmVsX2NvdW50IDwtIGxpc3QodGFidWxhdGUoZ3JvdXBfcGF0aFtbInBhdGhfSUQiXV1bZ3JvdXBfcm9vbVtbImRhdGFzZXRfSUQiXV0gPT0gMV0pLA0KICAgICAgICAgICAgICAgICAgICB0YWJ1bGF0ZShncm91cF9wYXRoW1sicGF0aF9JRCJdXVtncm91cF9yb29tW1siZGF0YXNldF9JRCJdXSA9PSAyXSksDQogICAgICAgICAgICAgICAgICAgIHRhYnVsYXRlKGdyb3VwX3BhdGhbWyJwYXRoX0lEIl1dW2dyb3VwX3Jvb21bWyJkYXRhc2V0X0lEIl1dID09IDNdKSkNCg0KIyBEw6l0ZXJtaW5hdGlvbiBkZXMgMTYgZGVybmnDqHJlcyBvYnNlcnZhdGlvbnMgKDIgc2Vjb25kZXMgw6AgOCBIeiksIG1veWVubmlzw6llcw0KZm9yIChpIGluIDE6MzE0KSB7DQogIHRlbXBfbGFiZWwgPC0gKChncm91cF9wYXRoW1sicGF0aF9JRCJdXVtpXSAtIDEpICogNjQpICsgMSAjIExpZ25lIGRlIGTDqW1hcnJhZ2UgZGFucyBsYSBtYXRyaWNlIGFncsOpZ8OpZQ0KICB0ZW1wX29icyA8LSBucm93KGRhdGFfcHJlW1tpXV0pIC0gMTUgIyBMaWduZSBkZSBkw6ltYXJyYWdlIGRhbnMgbGEgbWF0cmljZSDDoCBzYXV2ZWdhcmRlcg0KICANCiAgYXZnX3Nlcmllc19jbGVhbltbZ3JvdXBfcm9vbVtbImRhdGFzZXRfSUQiXV1baV1dXVt0ZW1wX2xhYmVsOih0ZW1wX2xhYmVsICsgNjMpLCAxXSA8LSBhdmdfc2VyaWVzX2NsZWFuW1tncm91cF9yb29tW1siZGF0YXNldF9JRCJdXVtpXV1dW3RlbXBfbGFiZWw6KHRlbXBfbGFiZWwgKyA2MyksIDFdICsgKHVubGlzdChkYXRhX3ByZVtbaV1dW3RlbXBfb2JzOih0ZW1wX29icyArIDE1KSwgXSkgLyBsYWJlbF9jb3VudFtbZ3JvdXBfcm9vbVtbImRhdGFzZXRfSUQiXV1baV1dXVtncm91cF9wYXRoW1sicGF0aF9JRCJdXVtpXV0pDQp9DQoNCiMgRMOpcGl2b3RhZ2UgZGUgbGEgdmFyaWFibGUgZMOpZmluaXNzYW50IGxhIHNhbGxlDQphdmdfc2VyaWVzX2NsZWFuIDwtIHJiaW5kKGF2Z19zZXJpZXNfY2xlYW5bWzFdXSwgYXZnX3Nlcmllc19jbGVhbltbMl1dLCBhdmdfc2VyaWVzX2NsZWFuW1szXV0pDQphdmdfc2VyaWVzX2NsZWFuW1siUm9vbSJdXSA8LSBhcy5mYWN0b3IoaW52ZXJzZS5ybGUobGlzdChsZW5ndGhzID0gcmVwKDM4NCwgMyksIHZhbHVlcyA9IDE6MykpKQ0KbGV2ZWxzKGF2Z19zZXJpZXNfY2xlYW5bWyJSb29tIl1dKSA8LSBwYXN0ZSgiU2FsbGUiLCAxOjMsIHNlcCA9ICIiKQ0KDQojIEFmZmljaGFnZSBzb3VzIGZvcm1lIGRlIHBsb3QgaW50ZXJhY3RpZiBkZSBtYW5pw6hyZSBhdXRvbWF0aXPDqWUNCmdncGxvdGx5KGdncGxvdChkYXRhID0gYXZnX3Nlcmllc19jbGVhbiwgYWVzX3N0cmluZyh4ID0gIlRpbWUiLCB5ID0gIlN0cmVuZ3RoIiwgZ3JvdXAgPSAiTGFiZWwiLCBjb2xvciA9ICJMYWJlbCIpKSArIGdlb21fbGluZSgpICsgZ2VvbV9wb2ludCgpICsgc2NhbGVfY29sb3JfYnJld2VyKHBhbGV0dGUgPSAiU2V0MiIpICsgdGhlbWVfYncoKSArIGZhY2V0X2dyaWQoQW5jaG9yIH4gUm9vbSkgKyBsYWJzKHRpdGxlID0gIkV2b2x1dGlvbiBkZSBsYSBmb3JjZSBkdSBzaWduYWwgZGUgbCdhbmNyZSAoY29ycmlnw6kpIHBhciByYXBwb3J0IGF1IHRlbXBzIiksIHdpZHRoID0gOTYwLCBoZWlnaHQgPSA3MjApDQoNCiMgRW5yZWdpc3RyZW1lbnQgZGUgbGEgdGFibGUgcG91ciB1c2FnZSB1bHTDqXJpZXVyIHNpIG7DqWNlc3NhaXJlDQpmd3JpdGUoYXZnX3Nlcmllc19jbGVhbiwgImFncmVnYXRpb24vYWdyZWdhdGlvbjIuY3N2IikNCg0KIyBUZW1wcyBuw6ljZXNzYWlyZQ0KdGltaW5nKEN1cnJlbnRUaW1lLCAiTmV0dG95YWdlIGRlcyBkb25uw6llcyIpDQpgYGANCg0KIyMgQ3LDqWF0aW9uIGRlcyBub3V2ZWxsZXMgZmVhdHVyZXMNCg0KTm91cyBwb3V2b25zIGTDqXNvcm1haXMgY3LDqWVyIGRlcyBmZWF0dXJlcyBuZXR0b3nDqWVzLiBDZWxhIG5vdXMgc2VydmlyYSBkYW5zIGxhIHByb2NoYWluZSBhbmFseXNlIHN5c3TDqW1pcXVlLg0KDQpgYGB7ciBDcmVhdGlvbkZlYXR1cmVzMn0NCiMgQ29tcHRldXIgZGUgdGVtcHMNCkN1cnJlbnRUaW1lIDwtIHRpbWVyKCkgIyBDaHVuayBDcsOpYXRpb24gZGVzIGZlYXR1cmVzIG5ldHRvecOpZXMNCg0KIyBQcsOpLWluaXRpYWxpc2F0aW9uIGRlIGxhIGZyYW1lDQptaW5pX2xtIDwtIGRhdGEuZnJhbWUobWF0cml4KG5yb3cgPSAzMTQsIG5jb2wgPSAzNikpDQoNCiMgQm91Y2xlIHBhciBzw6lyaWUgdGVtcG9yZWxsZQ0KZm9yIChpIGluIDE6MzE0KSB7DQogIA0KICAjIEJvdWNsZSBwYXIgYW5jcmUNCiAgZm9yIChqIGluIDE6NCkgew0KICAgIA0KICAgICMgRW50cmFpbmVtZW50IGQndW4gbW9kw6hsZSBsaW7DqWFpcmUgdXRpbGlzYW50IGxlcyBhdXRyZXMgYW5jcmVzLCBhdmVjIGwnaW50ZXJjZXB0cmljZQ0KICAgIHRlbXBfbW9kZWwgPC0gZmFzdExtUHVyZShYID0gY2JpbmQoYXMubWF0cml4KGRhdGFfcHJlW1tpXV1bLCAoMTo0KVstal0sIHdpdGggPSBGQUxTRV0pLCByZXAoMSwgbnJvdyhkYXRhX3ByZVtbaV1dKSkpLCB5ID0gZGF0YV9wcmVbW2ldXVtbal1dKQ0KICAgIA0KICAgICMgRW5yZWdpc3RyZW1lbnQgZGVzIGNvZWZmaWNpZW50cyBldCBkZXMgcsOpc2lkdXMNCiAgICBtaW5pX2xtW2ksIChqICogOCAtIDcpOihqICogOCldIDwtIGModGVtcF9tb2RlbCRjb2VmZmljaWVudHMsIHRlbXBfbW9kZWwkc3RkZXJyKQ0KICAgIA0KICB9DQogIA0KICAjIEFqb3V0IGR1IGRlcm5pZXIgw6lsw6ltZW50IGRlIGxhIHPDqXJpZSB0ZW1wb3JlbGxlICg0IGFuY3JlcykNCiAgbWluaV9sbVtpLCAzMzozNl0gPC0gZGF0YV9wcmVbW2ldXVtucm93KGRhdGFfcHJlW1tpXV0pLCBdDQogIA0KfQ0KDQojIEVucmVnaXN0cmVtZW50IGRlcyBkb25uw6llcyBhdSBmb3JtYXQgQ1NWDQpmd3JpdGUoY2JpbmQobWluaV9sbSwgR3JvdXAgPSBncm91cF9yb29tW1siZGF0YXNldF9JRCJdXSwgTGFiZWwgPSBncm91cF9wYXRoW1sicGF0aF9JRCJdXSksICJmZWF0dXJlcy9mZWF0dXJlczIuY3N2IikNCg0KIyBUZW1wcyBuw6ljZXNzYWlyZQ0KdGltaW5nKEN1cnJlbnRUaW1lLCAiQ3LDqWF0aW9uIGRlcyBmZWF0dXJlcyBuZXR0b3nDqWVzIikNCmBgYA0KDQojIFNlY29uZGUgQW5hbHlzZSBTeXN0w6ltaXF1ZQ0KDQpWdSBxdWUgbm91cyBkaXNwb3NvbnMgZGUgZmVhdHVyZXMgbmV0dG95w6llcywgbm91cyBwb3V2b25zIHByb2PDqWRlciDDoCBsJ2FuYWx5c2Ugc3lzdMOpbWlxdWUgZGUgesOpcm8uIENyw6nDqW9ucyBkJ2Fib3JkIGxlcyBkaWZmw6lyZW50cyBqZXV4IGRlIGRvbm7DqWVzLg0KDQpgYGB7ciBHZW5lcmF0aW9uRG9ubmVlczJ9DQojIENvbXB0ZXVyIGRlIHRlbXBzDQpDdXJyZW50VGltZSA8LSB0aW1lcigpICMgUHLDqXBhcmF0aW9uIGRlIGwnw6l2YWx1YXRpb24gZHUgbW9kw6hsZSBuZXR0b3nDqQ0KDQojIE/DuSBzYXV2ZWdhcmRlciBsZXMgZmljaGllcnMgPw0KZmlsZV90YWcgPC0gIjJfZGF0YS8iDQoNCiMgSW5pdGlhbGlzYXRpb24gZGUgbGEgdmFyaWFibGUgcXVpIGFjY3VlaWxsZXJhIGxhIHByw6ljaXNpb24NCmFjY3VyYWN5IDwtIGRhdGEuZnJhbWUobWF0cml4KG5yb3cgPSAxNiwgbmNvbCA9IDEzKSkNCmNvbG5hbWVzKGFjY3VyYWN5KSA8LSBjKCJGb2xkIiwgInhnYl9MaW5lYXJNb2RlbCIsICJ4Z2JfRGVjaXNpb25UcmVlIiwgInhnYl9SYW5kb21Gb3Jlc3QiLCAieGdiX0dyYWRpZW50Qm9vc3RpbmciLCAiaDJvX0xpbmVhck1vZGVsIiwgImgyb19EZWNpc2lvblRyZWUiLCAiaDJvX1JhbmRvbUZvcmVzdCIsICJoMm9fR3JhZGllbnRCb29zdGluZyIsICJoMm9fTk5fMzJ4Nl9SZUxVIiwgImgyb19OTl8zMng2X1NvZnQiLCAiaDJvX05OXzE2eDE2eDZfUmVMVSIsICJoMm9fTk5fMTZ4MTZ4Nl9Tb2Z0IikNCmFjY3VyYWN5WywgMV0gPC0gYygiRm9sZF8xdjIiLCAiRm9sZF8xdjMiLCAiRm9sZF8ydjEiLCAiRm9sZF8ydjMiLCAiRm9sZF8zdjEiLCAiRm9sZF8zdjIiLCAiRm9sZF8xdjIzIiwgIkZvbGRfMnYxMyIsICJGb2xkXzN2MTIiLCAiRm9sZF8xMnYzIiwgIkZvbGRfMTN2MiIsICJGb2xkXzIzdjEiLCAiTW95ZW5uZV8xYzEiLCAiTW95ZW5uZV8xYzIiLCAiTW95ZW5uZV8yYzEiLCAiTW95ZW5uZSIpDQoNCiMgSW5pdGlhbGlzYXRpb24gZGVzIGZvbGRzIHBvdXIgbGEgY3Jvc3MtdmFsaWRhdGlvbg0KZm9sZHNfdHJhaW4gPC0gbGlzdCgpDQpmb2xkc190ZXN0IDwtIGxpc3QoKQ0KdHJhaW5pbmdfZGF0YSA8LSBsaXN0KCkNCnRlc3RpbmdfZGF0YSA8LSBsaXN0KCkNCnRyYWluaW5nX3hnYiA8LSBsaXN0KCkNCnRlc3RpbmdfeGdiIDwtIGxpc3QoKQ0KdHJhaW5pbmdfaDJvIDwtIGxpc3QoKQ0KdGVzdGluZ19oMm8gPC0gbGlzdCgpDQpjb21iaW5hdGlvbnNfdHJhaW4gPC0gYyhsaXN0KDEsIDEsIDIsIDIsIDMsIDMpLCBjb21ibigzLCAxLCBzaW1wbGlmeSA9IEZBTFNFKSwgY29tYm4oMywgMiwgc2ltcGxpZnkgPSBGQUxTRSkpDQpjb21iaW5hdGlvbnNfdGVzdCA8LSBjKGxpc3QoMiwgMywgMSwgMywgMSwgMiksIHJldihjb21ibigzLCAyLCBzaW1wbGlmeSA9IEZBTFNFKSksIHJldihjb21ibigzLCAxLCBzaW1wbGlmeSA9IEZBTFNFKSkpDQp0ZW1wX2ZhY3RvcnMgPC0gYXMuZmFjdG9yKGdyb3VwX3BhdGgkcGF0aF9JRCkNCg0KIyBDcsOpYXRpb24gZGVzIGRvbm7DqWVzIGQnZW50cmFpbmVtZW50IGV0IGRlIHZhbGlkYXRpb24NCmZvciAoaSBpbiAxOjEyKSB7DQogIA0KICAjIENyw6lhdGlvbiBkZXMgZm9sZHMgZCdlbnRyYWluZW1lbnQgZXQgZGUgdmFsaWRhdGlvbg0KICBmb2xkc190cmFpbltbaV1dIDwtIHdoaWNoKGdyb3VwX3Jvb21bWyJkYXRhc2V0X0lEIl1dICVpbiUgY29tYmluYXRpb25zX3RyYWluW1tpXV0pDQogIGZvbGRzX3Rlc3RbW2ldXSA8LSB3aGljaChncm91cF9yb29tW1siZGF0YXNldF9JRCJdXSAlaW4lIGNvbWJpbmF0aW9uc190ZXN0W1tpXV0pDQogIA0KICAjIFJlY2hlcmNoZSBldCBzdXBwcmVzc2lvbiBkdSBsYWJlbCAzIGxvcnNxdWUgbGEgc2FsbGUgMSBlc3QgaXNvbMOpZSAoc29pdCBlbiB0cmFpbiBvbiBlbmzDqHZlIGVuIHRlc3QsIHNvaXQgZW4gdGVzdCBvbiBlbmzDqHZlIGVuIHRyYWluKQ0KICBpZiAoKGxlbmd0aChjb21iaW5hdGlvbnNfdHJhaW5bW2ldXSkgPT0gMSkgJiAoY29tYmluYXRpb25zX3RyYWluW1tpXV1bMV0gPT0gMSkpIHsNCiAgICBmb2xkc190ZXN0W1tpXV0gPC0gZm9sZHNfdGVzdFtbaV1dW2dyb3VwX3BhdGgkcGF0aF9JRFtmb2xkc190ZXN0W1tpXV1dICE9IDNdDQogIH0NCiAgaWYgKChsZW5ndGgoY29tYmluYXRpb25zX3Rlc3RbW2ldXSkgPT0gMSkgJiAoY29tYmluYXRpb25zX3Rlc3RbW2ldXVsxXSA9PSAxKSkgew0KICAgIGZvbGRzX3RyYWluW1tpXV0gPC0gZm9sZHNfdHJhaW5bW2ldXVtncm91cF9wYXRoJHBhdGhfSURbZm9sZHNfdHJhaW5bW2ldXV0gIT0gM10NCiAgfQ0KICANCiAgIyBDcsOpYXRpb24gZGVzIGRvbm7DqWVzIGQnZW50cmFpbmVtZW50IGV0IGRlIHZhbGlkYXRpb24NCiAgdHJhaW5pbmdfZGF0YVtbaV1dIDwtIG1pbmlfbG1bZm9sZHNfdHJhaW5bW2ldXSwgXQ0KICB0ZXN0aW5nX2RhdGFbW2ldXSA8LSBtaW5pX2xtW2ZvbGRzX3Rlc3RbW2ldXSwgXQ0KICANCiAgIyBFbnJlZ2lzdHJlbWVudCBkZXMgZG9ubsOpZXMgQ1NWDQogIGZ3cml0ZSh0cmFpbmluZ19kYXRhW1tpXV0sIHBhc3RlMChmaWxlX3RhZywgInRyYWluTkxfIiwgc3ByaW50ZigiJTAyZCIsIGkpLCAiLmNzdiIpKQ0KICBmd3JpdGUodGVzdGluZ19kYXRhW1tpXV0sIHBhc3RlMChmaWxlX3RhZywgInRlc3ROTF8iLCBzcHJpbnRmKCIlMDJkIiwgaSksICIuY3N2IikpDQogIA0KICAjIFRyYW5zZm9ybWF0aW9uIGRlcyBkb25uw6llcyBhdSBmb3JtYXQgYXBwcm9wcmnDqSBwb3VyIHhnYm9vc3QNCiAgdHJhaW5pbmdfeGdiW1tpXV0gPC0geGdiLkRNYXRyaXgoZGF0YSA9IGFzLm1hdHJpeCh0cmFpbmluZ19kYXRhW1tpXV0pLCBsYWJlbCA9IGdyb3VwX3BhdGgkcGF0aF9JRFtmb2xkc190cmFpbltbaV1dXSAtIDEpDQogIHRlc3RpbmdfeGdiW1tpXV0gPC0geGdiLkRNYXRyaXgoZGF0YSA9IGFzLm1hdHJpeCh0ZXN0aW5nX2RhdGFbW2ldXSksIGxhYmVsID0gZ3JvdXBfcGF0aCRwYXRoX0lEW2ZvbGRzX3Rlc3RbW2ldXV0gLSAxKQ0KICANCiAgIyBEdW1waW5nIGRlcyBkYXRhc2V0cyBiaW5haXJlcyB4Z2Jvb3N0DQogIHhnYi5ETWF0cml4LnNhdmUodHJhaW5pbmdfeGdiW1tpXV0sIHBhc3RlMChmaWxlX3RhZywgInRyYWluTF8iLCBzcHJpbnRmKCIlMDJkIiwgaSksICIuZGF0YSIpKQ0KICB4Z2IuRE1hdHJpeC5zYXZlKHRlc3RpbmdfeGdiW1tpXV0sIHBhc3RlMChmaWxlX3RhZywgInRlc3RMXyIsIHNwcmludGYoIiUwMmQiLCBpKSwgIi5kYXRhIikpDQogIA0KICAjIFRyYW5zZm9ybWF0aW9uIGRlcyBkb25uw6llcyBhdSBmb3JtYXQgYXBwcm9wcmnDqSBwb3VyIEgyTw0KICB0cmFpbmluZ19oMm9bW2ldXSA8LSBhcy5oMm8oY2JpbmQoTGFiZWwgPSB0ZW1wX2ZhY3RvcnNbZm9sZHNfdHJhaW5bW2ldXV0sIHRyYWluaW5nX2RhdGFbW2ldXSkpDQogIHRlc3RpbmdfaDJvW1tpXV0gPC0gYXMuaDJvKGNiaW5kKExhYmVsID0gdGVtcF9mYWN0b3JzW2ZvbGRzX3Rlc3RbW2ldXV0sIHRlc3RpbmdfZGF0YVtbaV1dKSkNCiAgDQogICMgRW5yZWdpc3RyZW1lbnQgZGVzIGZyYW1lcyBIMk8gKENTViArIExhYmVsKQ0KICBoMm8uZXhwb3J0RmlsZSh0cmFpbmluZ19oMm9bW2ldXSwgcGFzdGUwKGZpbGVfdGFnLCAidHJhaW5MXyIsIHNwcmludGYoIiUwMmQiLCBpKSwgIi5jc3YiKSwgZm9yY2UgPSBUUlVFKQ0KICBoMm8uZXhwb3J0RmlsZSh0ZXN0aW5nX2gyb1tbaV1dLCBwYXN0ZTAoZmlsZV90YWcsICJ0ZXN0TF8iLCBzcHJpbnRmKCIlMDJkIiwgaSksICIuY3N2IiksIGZvcmNlID0gVFJVRSkNCiAgDQp9DQoNCiMgVGVtcHMgbsOpY2Vzc2FpcmUNCnRpbWluZyhDdXJyZW50VGltZSwgIlByw6lwYXJhdGlvbiBkZSBsJ8OpdmFsdWF0aW9uIGR1IG1vZMOobGUgbmV0dG95w6kiKQ0KYGBgDQoNCiMjIE5vdXZlbCBlbnRyYcOubmVtZW50IGRlcyBkb3V6ZSBtb2TDqGxlcw0KDQpOb3VzIHBvdXZvbnMgcmVsYW5jZXIgbGVzIGNhbGN1bHMgcG91ciBkw6l0ZXJtaW5lciBsYSBwZXJmb3JtYW5jZSBkZXMgbW9kw6hsZXMuIFVuIGVudHJhaW5lbWVudCBkZSB0b3VzIGxlcyBtb2TDqGxlcyBlc3QgYmllbiBzw7tyIG7DqWNlc3NhaXJlLCBhaW5zaSBxdWUgbGEgdmFsaWRhdGlvbiBkZSBsZXVycyBwZXJmb3JtYW5jZXMgc3VyIGRlcyDDqWNoYW50aWxsb25zICJpbmNvbm51cyIuDQoNCmBgYHtyIEVudHJhaW5lbWVudDIsIGNhY2hlPVRSVUV9DQojIENvbXB0ZXVyIGRlIHRlbXBzDQpDdXJyZW50VGltZSA8LSB0aW1lcigpICMgQ2h1bmsgQ3LDqWF0aW9uIGV0IMOpdmFsdWF0aW9uIGRlcyBkb3V6ZSBtb2TDqGxlcyBkZSBiZW5jaG1hcmsgbmV0dG95w6kNCg0KIyBPw7kgc2F1dmVnYXJkZXIgbGVzIGZpY2hpZXJzID8NCmZpbGVfdGFnIDwtICIyX21vZGVscy8iDQpmaWxlX2gybyA8LSAiMl9tb2RlbHMiDQoNCiMgQm91Y2xlIGQnw6l2YWx1YXRpb24NCmZvciAoaSBpbiAxOjEyKSB7DQogIA0KICAjIEVudHJhaW5lbWVudCBkdSBtb2TDqGxlIGRlIHLDqWdyZXNzaW9uIGxvZ2lzdGlxdWUgKHhnYm9vc3QpDQogIHRlbXBfbW9kZWwgPC0geGdiX2R5bmFtaWNfdHJhaW4odHJhaW4gPSB0cmFpbmluZ194Z2JbW2ldXSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0ZXN0ID0gdGVzdGluZ194Z2JbW2ldXSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBib29zdGVyID0gImdibGluZWFyIiwgIyBMaW7DqWFpcmUNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBucm91bmRzID0gMTAwMDAwMCwgIyBBcnLDqnTDqSBhdSBtZWlsbGV1ciByw6lzdWx0YXQNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBudW1fcGFyYWxsZWxfdHJlZXMgPSAxKQ0KICB4Z2IuZHVtcChtb2RlbCA9IHRlbXBfbW9kZWwsICMgTW9kw6hsZSDDoCBlbnJlZ2lzdHJlcg0KICAgICAgICAgICBmbmFtZSA9IHBhc3RlMChmaWxlX3RhZywgInhnYl9nbG1fIiwgc3ByaW50ZigiJTAyZCIsIGkpLCAiLmpzb24iKSwgIyBPw7kgZW5yZWdpc3RyZXIgbGUgbW9kw6hsZSA/DQogICAgICAgICAgIHdpdGhfc3RhdHMgPSBUUlVFLCAjIEVucmVnaXN0cmVtZW50IGRlcyBzdGF0aXN0aXF1ZXMgc2kgbW9kw6hsZSBnYnRyZWUNCiAgICAgICAgICAgZHVtcF9mb3JtYXQgPSAianNvbiIpICMgRHVtcCBhdSBmb3JtYXQganNvbiwgcsOpLXV0aWxpc2FibGUNCiAgYWNjdXJhY3lbaSwgMl0gPC0gMSAtIHRlbXBfbW9kZWwkZXZhbHVhdGlvbl9sb2dbWzJdXVt0ZW1wX21vZGVsJGJlc3RfaXRlcmF0aW9uXSAjIFLDqWN1cMOpcmF0aW9uIGR1IG1laWxsZXVyIHLDqXN1bHRhdA0KICANCiAgIyBFbnRyYWluZW1lbnQgZHUgbW9kw6hsZSBkJ2FyYnJlIGRlIGTDqWNpc2lvbiAoeGdib29zdCkNCiAgdGVtcF9tb2RlbCA8LSB4Z2JfZHluYW1pY190cmFpbih0cmFpbiA9IHRyYWluaW5nX3hnYltbaV1dLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRlc3QgPSB0ZXN0aW5nX3hnYltbaV1dLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJvb3N0ZXIgPSAiZ2J0cmVlIiwgIyBOb24tbGluw6lhaXJlDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbnJvdW5kcyA9IDEsICMgVW4gc2V1bCBhcmJyZQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG51bV9wYXJhbGxlbF90cmVlcyA9IDEpDQogIHhnYi5kdW1wKG1vZGVsID0gdGVtcF9tb2RlbCwgIyBNb2TDqGxlIMOgIGVucmVnaXN0cmVyDQogICAgICAgICAgIGZuYW1lID0gcGFzdGUwKGZpbGVfdGFnLCAieGdiX2R0XyIsIHNwcmludGYoIiUwMmQiLCBpKSwgIi5qc29uIiksICMgT8O5IGVucmVnaXN0cmVyIGxlIG1vZMOobGUgPw0KICAgICAgICAgICB3aXRoX3N0YXRzID0gVFJVRSwgIyBFbnJlZ2lzdHJlbWVudCBkZXMgc3RhdGlzdGlxdWVzIHNpIG1vZMOobGUgZ2J0cmVlDQogICAgICAgICAgIGR1bXBfZm9ybWF0ID0gImpzb24iKSAjIER1bXAgYXUgZm9ybWF0IGpzb24sIHLDqS11dGlsaXNhYmxlDQogIGFjY3VyYWN5W2ksIDNdIDwtIDEgLSB0ZW1wX21vZGVsJGV2YWx1YXRpb25fbG9nW1syXV1bMV0gIyBSw6ljdXDDqXJhdGlvbiBkdSBtZWlsbGV1ciByw6lzdWx0YXQNCiAgDQogICMgRW50cmFpbmVtZW50IGR1IG1vZMOobGUgZGUgUmFuZG9tIEZvcmVzdCAoeGdib29zdCkNCiAgdGVtcF9tb2RlbCA8LSB4Z2JfZHluYW1pY190cmFpbih0cmFpbiA9IHRyYWluaW5nX3hnYltbaV1dLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRlc3QgPSB0ZXN0aW5nX3hnYltbaV1dLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJvb3N0ZXIgPSAiZ2J0cmVlIiwgIyBOb24tbGluw6lhaXJlDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbnJvdW5kcyA9IDEsICMgVW5lIHNldWxlIGl0w6lyYXRpb24NCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBudW1fcGFyYWxsZWxfdHJlZXMgPSAyMDApICMgRGUgMjAwIGFyYnJlcw0KICB4Z2IuZHVtcChtb2RlbCA9IHRlbXBfbW9kZWwsICMgTW9kw6hsZSDDoCBlbnJlZ2lzdHJlcg0KICAgICAgICAgICBmbmFtZSA9IHBhc3RlMChmaWxlX3RhZywgInhnYl9yZl8iLCBzcHJpbnRmKCIlMDJkIiwgaSksICIuanNvbiIpLCAjIE/DuSBlbnJlZ2lzdHJlciBsZSBtb2TDqGxlID8NCiAgICAgICAgICAgd2l0aF9zdGF0cyA9IFRSVUUsICMgRW5yZWdpc3RyZW1lbnQgZGVzIHN0YXRpc3RpcXVlcyBzaSBtb2TDqGxlIGdidHJlZQ0KICAgICAgICAgICBkdW1wX2Zvcm1hdCA9ICJqc29uIikgIyBEdW1wIGF1IGZvcm1hdCBqc29uLCByw6ktdXRpbGlzYWJsZQ0KICBhY2N1cmFjeVtpLCA0XSA8LSAxIC0gdGVtcF9tb2RlbCRldmFsdWF0aW9uX2xvZ1tbMl1dICMgUsOpY3Vww6lyYXRpb24gZHUgbWVpbGxldXIgcsOpc3VsdGF0DQogIA0KICAjIEVudHJhaW5lbWVudCBkdSBtb2TDqGxlIGQnYXJicmUgZGUgZMOpY2lzaW9uIGJvb3N0w6kgYXZlYyBwcm90ZWN0aW9uIGNvbnRyZSBsJ292ZXJmaXR0aW5nICh4Z2Jvb3N0KQ0KICB0ZW1wX21vZGVsIDwtIHhnYl9keW5hbWljX3RyYWluKHRyYWluID0gdHJhaW5pbmdfeGdiW1tpXV0sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGVzdCA9IHRlc3RpbmdfeGdiW1tpXV0sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYm9vc3RlciA9ICJnYnRyZWUiLCAjIE5vbi1saW7DqWFpcmUNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBucm91bmRzID0gMTAwMDAwMCwgIyBBcnLDqnTDqSBhdSBtZWlsbGV1ciByw6lzdWx0YXQNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBudW1fcGFyYWxsZWxfdHJlZXMgPSAxKQ0KICB4Z2IuZHVtcChtb2RlbCA9IHRlbXBfbW9kZWwsICMgTW9kw6hsZSDDoCBlbnJlZ2lzdHJlcg0KICAgICAgICAgICBmbmFtZSA9IHBhc3RlMChmaWxlX3RhZywgInhnYl9nYnRfIiwgc3ByaW50ZigiJTAyZCIsIGkpLCAiLmpzb24iKSwgIyBPw7kgZW5yZWdpc3RyZXIgbGUgbW9kw6hsZSA/DQogICAgICAgICAgIHdpdGhfc3RhdHMgPSBUUlVFLCAjIEVucmVnaXN0cmVtZW50IGRlcyBzdGF0aXN0aXF1ZXMgc2kgbW9kw6hsZSBnYnRyZWUNCiAgICAgICAgICAgZHVtcF9mb3JtYXQgPSAianNvbiIpICMgRHVtcCBhdSBmb3JtYXQganNvbiwgcsOpLXV0aWxpc2FibGUNCiAgYWNjdXJhY3lbaSwgNV0gPC0gMSAtIHRlbXBfbW9kZWwkZXZhbHVhdGlvbl9sb2dbWzJdXVt0ZW1wX21vZGVsJGJlc3RfaXRlcmF0aW9uXSAjIFLDqWN1cMOpcmF0aW9uIGR1IG1laWxsZXVyIHLDqXN1bHRhdA0KICANCiAgIyBFbnRyYWluZW1lbnQgZHUgbW9kw6hsZSBkZSByw6lncmVzc2lvbiBsb2dpc3RpcXVlIChoMm8pDQogIHRlbXBfbW9kZWwgPC0gaDJvLmdsbSh5ID0gMSwNCiAgICAgICAgICAgICAgICAgICAgICAgIHRyYWluaW5nX2ZyYW1lID0gdHJhaW5pbmdfaDJvW1tpXV0sDQogICAgICAgICAgICAgICAgICAgICAgICB2YWxpZGF0aW9uX2ZyYW1lID0gdGVzdGluZ19oMm9bW2ldXSwNCiAgICAgICAgICAgICAgICAgICAgICAgIG1vZGVsX2lkID0gcGFzdGUwKCJoMm9fZ2xtXyIsIHNwcmludGYoIiUwMmQiLCBpKSksICMgTm9tIGR1IG1vZMOobGUNCiAgICAgICAgICAgICAgICAgICAgICAgIG1heF9pdGVyYXRpb25zID0gMTAwLCAjIDEwMCBpdMOpcmF0aW9ucyBkJ29wdGltaXNhdGlvbg0KICAgICAgICAgICAgICAgICAgICAgICAgc29sdmVyID0gIklSTFNNIiwgIyBTb2x2ZXVyIHBhciBkw6lmYXV0DQogICAgICAgICAgICAgICAgICAgICAgICBzdGFuZGFyZGl6ZSA9IEZBTFNFLCAjIFBhcyBkZSBzdGFuZGFyZGlzYXRpb24gcHVpc3F1ZSBbLTEsIDFdDQogICAgICAgICAgICAgICAgICAgICAgICBmYW1pbHkgPSAibXVsdGlub21pYWwiLCAjIENsYXNzaWZpY2F0aW9uIG11bHRpLWNsYXNzZQ0KICAgICAgICAgICAgICAgICAgICAgICAgc2VlZCA9IDAsICMgUmVwcm9kdWN0aW9uIGRlcyByw6lzdWx0YXRzDQogICAgICAgICAgICAgICAgICAgICAgICBpbnRlcmNlcHQgPSBUUlVFKQ0KICBoMm8uZG93bmxvYWRfcG9qbyh0ZW1wX21vZGVsLCAjIE1vZMOobGUgw6AgZW5yZWdpc3RyZXINCiAgICAgICAgICAgICAgICAgICAgcGF0aCA9IGZpbGVfaDJvLCAjIE/DuSBlbnJlZ2lzdHJlciBsZSBtb2TDqGxlID8NCiAgICAgICAgICAgICAgICAgICAgZ2V0X2phciA9IEZBTFNFKSAjIFBhcyBkZSBmaWNoaWVyIC5qYXINCiAgYWNjdXJhY3lbaSwgNl0gPC0gdGVtcF9tb2RlbEBtb2RlbCR2YWxpZGF0aW9uX21ldHJpY3NAbWV0cmljcyRoaXRfcmF0aW9fdGFibGVbMSwgMl0NCiAgDQogICMgRW50cmFpbmVtZW50IGR1IG1vZMOobGUgZCdhcmJyZSBkZSBkw6ljaXNpb24gKGgybykNCiAgdGVtcF9tb2RlbCA8LSBoMm8ucmFuZG9tRm9yZXN0KHkgPSAxLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdHJhaW5pbmdfZnJhbWUgPSB0cmFpbmluZ19oMm9bW2ldXSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHZhbGlkYXRpb25fZnJhbWUgPSB0ZXN0aW5nX2gyb1tbaV1dLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbW9kZWxfaWQgPSBwYXN0ZTAoImgyb19kdF8iLCBzcHJpbnRmKCIlMDJkIiwgaSkpLCAjIE5vbSBkdSBtb2TDqGxlDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzYW1wbGVfcmF0ZSA9IDEsICMgVG91dGVzIGxlcyBvYnNlcnZhdGlvbnMgc2Vyb250IHByaXNlcyBlbiBjb21wdGUgcG91ciBsZSBzZXVsIGFyYnJlIGRlIGTDqWNpc2lvbg0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbXRyaWVzID0gMzYsICMgVG91dGVzIGxlcyBmZWF0dXJlcyBzZXJvbnQgcHJpc2VzIGVuIGNvbXB0ZSBwb3VyIGxlIHNldWwgYXJicmUgZGUgZMOpY2lzaW9uDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBudHJlZXMgPSAxLCAjIFVuIHNldWwgYXJicmUNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNlZWQgPSAwKSAjIFJlcHJvZHVjdGlvbiBkZXMgcsOpc3VsdGF0cw0KICBoMm8uZG93bmxvYWRfcG9qbyh0ZW1wX21vZGVsLCAjIE1vZMOobGUgw6AgZW5yZWdpc3RyZXINCiAgICAgICAgICAgICAgICAgICAgcGF0aCA9IGZpbGVfaDJvLCAjIE/DuSBlbnJlZ2lzdHJlciBsZSBtb2TDqGxlID8NCiAgICAgICAgICAgICAgICAgICAgZ2V0X2phciA9IEZBTFNFKSAjIFBhcyBkZSBmaWNoaWVyIC5qYXINCiAgYWNjdXJhY3lbaSwgN10gPC0gMSAtIG1pbih0ZW1wX21vZGVsQG1vZGVsJHNjb3JpbmdfaGlzdG9yeSR2YWxpZGF0aW9uX2NsYXNzaWZpY2F0aW9uX2Vycm9yLCBuYS5ybSA9IFRSVUUpDQogIA0KICAjIEVudHJhaW5lbWVudCBkdSBtb2TDqGxlIGRlIFJhbmRvbSBGb3Jlc3QgKGgybykNCiAgdGVtcF9tb2RlbCA8LSBoMm8ucmFuZG9tRm9yZXN0KHkgPSAxLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdHJhaW5pbmdfZnJhbWUgPSB0cmFpbmluZ19oMm9bW2ldXSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHZhbGlkYXRpb25fZnJhbWUgPSB0ZXN0aW5nX2gyb1tbaV1dLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbW9kZWxfaWQgPSBwYXN0ZTAoImgyb19yZl8iLCBzcHJpbnRmKCIlMDJkIiwgaSkpLCAjIE5vbSBkdSBtb2TDqGxlDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzYW1wbGVfcmF0ZSA9IDAuNjMyLCAjIEJvb3RzdHJhcHBpbmcgLjYzMiBwb3VyIGNoYXF1ZSBhcmJyZSBkZSBkw6ljaXNpb24NCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG10cmllcyA9IC0xLCAjIHNxcnQoMzYpIGZlYXR1cmVzIHNlcm9udCBwcmlzZXMgZW4gY29tcHRlIHBvdXIgY2hhcXVlIGFyYnJlIGRlIGTDqWNpc2lvbg0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbnRyZWVzID0gMjAwLCAjIDIwMCBhcmJyZXMNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNlZWQgPSAwKSAjIFJlcHJvZHVjdGlvbiBkZXMgcsOpc3VsdGF0cw0KICBoMm8uZG93bmxvYWRfcG9qbyh0ZW1wX21vZGVsLCAjIE1vZMOobGUgw6AgZW5yZWdpc3RyZXINCiAgICAgICAgICAgICAgICAgICAgcGF0aCA9IGZpbGVfaDJvLCAjIE/DuSBlbnJlZ2lzdHJlciBsZSBtb2TDqGxlID8NCiAgICAgICAgICAgICAgICAgICAgZ2V0X2phciA9IEZBTFNFKSAjIFBhcyBkZSBmaWNoaWVyIC5qYXINCiAgYWNjdXJhY3lbaSwgOF0gPC0gMSAtIG1pbih0ZW1wX21vZGVsQG1vZGVsJHNjb3JpbmdfaGlzdG9yeSR2YWxpZGF0aW9uX2NsYXNzaWZpY2F0aW9uX2Vycm9yLCBuYS5ybSA9IFRSVUUpDQogIA0KICAjIEVudHJhaW5lbWVudCBkdSBtb2TDqGxlIGQnYXJicmUgZGUgZMOpY2lzaW9uIGJvb3N0w6kgYXZlYyBwcm90ZWN0aW9uIGNvbnRyZSBsJ292ZXJmaXR0aW5nIChoMm8pDQogIHRlbXBfbW9kZWwgPC0gaDJvLmdibSh5ID0gMSwNCiAgICAgICAgICAgICAgICAgICAgICB0cmFpbmluZ19mcmFtZSA9IHRyYWluaW5nX2gyb1tbaV1dLA0KICAgICAgICAgICAgICAgICAgICAgIHZhbGlkYXRpb25fZnJhbWUgPSB0ZXN0aW5nX2gyb1tbaV1dLA0KICAgICAgICAgICAgICAgICAgICAgIG1vZGVsX2lkID0gcGFzdGUwKCJoMm9fZ2J0XyIsIHNwcmludGYoIiUwMmQiLCBpKSksICMgTm9tIGR1IG1vZMOobGUNCiAgICAgICAgICAgICAgICAgICAgICBkaXN0cmlidXRpb24gPSAibXVsdGlub21pYWwiLCAjIENsYXNzaWZpY2F0aW9uIG11bHRpLWNsYXNzZQ0KICAgICAgICAgICAgICAgICAgICAgIHNhbXBsZV9yYXRlID0gMSwgIyBQYXMgZGUgcHJvY2Vzc3VzIHN0b2NoYXN0aXF1ZQ0KICAgICAgICAgICAgICAgICAgICAgIG50cmVlcyA9IDEwMCwgIyAxMDAgaXTDqXJhdGlvbnMgZGUgYm9vc3RpbmcgYXUgbWF4aW11bQ0KICAgICAgICAgICAgICAgICAgICAgIHNjb3JlX2VhY2hfaXRlcmF0aW9uID0gVFJVRSwgIyBOb3RlciBsYSB2YWxldXIgZGUgY2hhcXVlIGl0w6lyYXRpb24NCiAgICAgICAgICAgICAgICAgICAgICBzdG9wcGluZ19yb3VuZHMgPSAxMCwgIyBBcnLDqnQgYXByw6hzIDEwIGl0w6lyYXRpb25zIHNhbnMgYW3DqWxpb3JhdG9uIGRlIGxhIG3DqXRyaXF1ZQ0KICAgICAgICAgICAgICAgICAgICAgIHN0b3BwaW5nX21ldHJpYyA9ICJtaXNjbGFzc2lmaWNhdGlvbiIsICMgU3VydmVpbGxlciBsJ2luZXhhY3RpdHVkZSBkZSBsYSBjbGFzc2lmaWNhdGlvbiBwb3VyIGwnYXJyw6p0DQogICAgICAgICAgICAgICAgICAgICAgc3RvcHBpbmdfdG9sZXJhbmNlID0gMC4wMDAwMSwgIyBBcnLDqnRlciBsb3JzcXVlIGxhIG3DqXRyaXF1ZSBzdGFnbmUgZGUgMC4wMDElDQogICAgICAgICAgICAgICAgICAgICAgc2VlZCA9IDApICMgUmVwcm9kdWN0aW9uIGRlcyByw6lzdWx0YXRzDQogIGgyby5kb3dubG9hZF9wb2pvKHRlbXBfbW9kZWwsICMgTW9kw6hsZSDDoCBlbnJlZ2lzdHJlcg0KICAgICAgICAgICAgICAgICAgICBwYXRoID0gZmlsZV9oMm8sICMgT8O5IGVucmVnaXN0cmVyIGxlIG1vZMOobGUgPw0KICAgICAgICAgICAgICAgICAgICBnZXRfamFyID0gRkFMU0UpICMgUGFzIGRlIGZpY2hpZXIgLmphcg0KICBhY2N1cmFjeVtpLCA5XSA8LSAxIC0gbWluKHRlbXBfbW9kZWxAbW9kZWwkc2NvcmluZ19oaXN0b3J5JHZhbGlkYXRpb25fY2xhc3NpZmljYXRpb25fZXJyb3IsIG5hLnJtID0gVFJVRSkNCiAgDQogICMgRW50cmFpbmVtZW50IGR1IHLDqXNlYXUgZGUgbmV1cm9uZXMgw6AgYXJjaGl0ZWN0dXJlIDMyeDYgKyBSZUxVIChoMm8pDQogIHRlbXBfbW9kZWwgPC0gaDJvX25uX3RyYWluKHRyYWluID0gdHJhaW5pbmdfaDJvW1tpXV0sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRlc3QgPSB0ZXN0aW5nX2gyb1tbaV1dLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtb2RlbF9pZCA9IHBhc3RlMCgiaDJvX25uXzMyeDZfUmVMVV8iLCBzcHJpbnRmKCIlMDJkIiwgaSkpLCAjIE5vbSBkdSBtb2TDqGxlDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFjdGl2YXRpb24gPSAiUmVjdGlmaWVyIiwgIyBSZUxVDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhpZGRlbiA9IDMyKSAjIEFyY2hpdGVjdHVyZSAzMng2DQogIGgyby5kb3dubG9hZF9wb2pvKHRlbXBfbW9kZWwsICMgTW9kw6hsZSDDoCBlbnJlZ2lzdHJlcg0KICAgICAgICAgICAgICAgICAgICBwYXRoID0gZmlsZV9oMm8sICMgT8O5IGVucmVnaXN0cmVyIGxlIG1vZMOobGUgPw0KICAgICAgICAgICAgICAgICAgICBnZXRfamFyID0gRkFMU0UpICMgUGFzIGRlIGZpY2hpZXIgLmphcg0KICBhY2N1cmFjeVtpLCAxMF0gPC0gMSAtIG1pbih0ZW1wX21vZGVsQG1vZGVsJHNjb3JpbmdfaGlzdG9yeSR2YWxpZGF0aW9uX2NsYXNzaWZpY2F0aW9uX2Vycm9yLCBuYS5ybSA9IFRSVUUpDQogIA0KICAjIEVudHJhaW5lbWVudCBkdSByw6lzZWF1IGRlIG5ldXJvbmVzIMOgIGFyY2hpdGVjdHVyZSAzMng2ICsgVGFuaCAoaDJvKQ0KICB0ZW1wX21vZGVsIDwtIGgyb19ubl90cmFpbih0cmFpbiA9IHRyYWluaW5nX2gyb1tbaV1dLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0ZXN0ID0gdGVzdGluZ19oMm9bW2ldXSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbW9kZWxfaWQgPSBwYXN0ZTAoImgyb19ubl8zMng2X1RhbmhfIiwgc3ByaW50ZigiJTAyZCIsIGkpKSwgIyBOb20gZHUgbW9kw6hsZQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhY3RpdmF0aW9uID0gIlRhbmgiLCAjICJTaWdtb2lkZSINCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaGlkZGVuID0gMzIpICMgQXJjaGl0ZWN0dXJlIDMyeDYNCiAgaDJvLmRvd25sb2FkX3Bvam8odGVtcF9tb2RlbCwgIyBNb2TDqGxlIMOgIGVucmVnaXN0cmVyDQogICAgICAgICAgICAgICAgICAgIHBhdGggPSBmaWxlX2gybywgIyBPw7kgZW5yZWdpc3RyZXIgbGUgbW9kw6hsZSA/DQogICAgICAgICAgICAgICAgICAgIGdldF9qYXIgPSBGQUxTRSkgIyBQYXMgZGUgZmljaGllciAuamFyDQogIGFjY3VyYWN5W2ksIDExXSA8LSAxIC0gbWluKHRlbXBfbW9kZWxAbW9kZWwkc2NvcmluZ19oaXN0b3J5JHZhbGlkYXRpb25fY2xhc3NpZmljYXRpb25fZXJyb3IsIG5hLnJtID0gVFJVRSkNCiAgDQogICMgRW50cmFpbmVtZW50IGR1IHLDqXNlYXUgZGUgbmV1cm9uZXMgw6AgYXJjaGl0ZWN0dXJlIDE2eDE2eDYgKyBSZUxVIChoMm8pDQogIHRlbXBfbW9kZWwgPC0gaDJvX25uX3RyYWluKHRyYWluID0gdHJhaW5pbmdfaDJvW1tpXV0sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRlc3QgPSB0ZXN0aW5nX2gyb1tbaV1dLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtb2RlbF9pZCA9IHBhc3RlMCgiaDJvX25uXzE2eDE2eDZfUmVMVV8iLCBzcHJpbnRmKCIlMDJkIiwgaSkpLCAjIE5vbSBkdSBtb2TDqGxlDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFjdGl2YXRpb24gPSAiUmVjdGlmaWVyIiwgIyBSZUxVDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhpZGRlbiA9IGMoMTYsIDE2KSkgIyBBcmNoaXRlY3R1cmUgMTZ4MTZ4Ng0KICBoMm8uZG93bmxvYWRfcG9qbyh0ZW1wX21vZGVsLCAjIE1vZMOobGUgw6AgZW5yZWdpc3RyZXINCiAgICAgICAgICAgICAgICAgICAgcGF0aCA9IGZpbGVfaDJvLCAjIE/DuSBlbnJlZ2lzdHJlciBsZSBtb2TDqGxlID8NCiAgICAgICAgICAgICAgICAgICAgZ2V0X2phciA9IEZBTFNFKSAjIFBhcyBkZSBmaWNoaWVyIC5qYXINCiAgYWNjdXJhY3lbaSwgMTJdIDwtIDEgLSBtaW4odGVtcF9tb2RlbEBtb2RlbCRzY29yaW5nX2hpc3RvcnkkdmFsaWRhdGlvbl9jbGFzc2lmaWNhdGlvbl9lcnJvciwgbmEucm0gPSBUUlVFKQ0KICANCiAgIyBFbnRyYWluZW1lbnQgZHUgcsOpc2VhdSBkZSBuZXVyb25lcyDDoCBhcmNoaXRlY3R1cmUgMTZ4MTZ4NiArIFRhbmggKGgybykNCiAgdGVtcF9tb2RlbCA8LSBoMm9fbm5fdHJhaW4odHJhaW4gPSB0cmFpbmluZ19oMm9bW2ldXSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGVzdCA9IHRlc3RpbmdfaDJvW1tpXV0sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1vZGVsX2lkID0gcGFzdGUwKCJoMm9fbm5fMTZ4MTZ4Nl9UYW5oXyIsIHNwcmludGYoIiUwMmQiLCBpKSksICMgTm9tIGR1IG1vZMOobGUNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYWN0aXZhdGlvbiA9ICJUYW5oIiwgIyAiU2lnbW9pZGUiDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhpZGRlbiA9IGMoMTYsIDE2KSkgIyBBcmNoaXRlY3R1cmUgMTZ4MTZ4Ng0KICBoMm8uZG93bmxvYWRfcG9qbyh0ZW1wX21vZGVsLCAjIE1vZMOobGUgw6AgZW5yZWdpc3RyZXINCiAgICAgICAgICAgICAgICAgICAgcGF0aCA9IGZpbGVfaDJvLCAjIE/DuSBlbnJlZ2lzdHJlciBsZSBtb2TDqGxlID8NCiAgICAgICAgICAgICAgICAgICAgZ2V0X2phciA9IEZBTFNFKSAjIFBhcyBkZSBmaWNoaWVyIC5qYXINCiAgYWNjdXJhY3lbaSwgMTNdIDwtIDEgLSBtaW4odGVtcF9tb2RlbEBtb2RlbCRzY29yaW5nX2hpc3RvcnkkdmFsaWRhdGlvbl9jbGFzc2lmaWNhdGlvbl9lcnJvciwgbmEucm0gPSBUUlVFKQ0KICANCn0NCg0KIyBUZW1wcyBuw6ljZXNzYWlyZQ0KdGltaW5nKEN1cnJlbnRUaW1lLCAiQ3LDqWF0aW9uIGV0IMOpdmFsdWF0aW9uIGRlcyBkb3V6ZSBtb2TDqGxlcyBkZSBiZW5jaG1hcmsgbmV0dG95w6kiKQ0KYGBgDQoNCiMjIE5vdXZlbGxlIGFuYWx5c2UgZGVzIHLDqXN1bHRhdHMNCg0KTm91cyB2b3lvbnMgY2xhaXJlbWVudCBxdWUgbGVzIG1vZMOobGVzIGxpbsOpYWlyZXMgc2UgZMOpbWFycXVlbnQgZGUgdG91cyBsZXMgYXV0cmVzIG1vZMOobGVzIG5vbi1saW7DqWFpcmVzLiBJbCB5IGEgZXUgdW5lIHJhaXNvbiB0b3V0ZSBzaW1wbGUgw6AgYSBjZWxhIDogbGVzIG1vZMOobGVzIG5vbi1saW7DqWFpcmVzIHV0aWxpc8OpcyBuZSBwZXV2ZW50IHMnYWNjb21vZGVyIGZhY2Ugw6AgZGUgbm91dmVsbGVzIHZhbGV1cnMgZGUgbWFuacOocmUgZWNoZWxvbm7DqWUsIGNlIHF1aSBlc3QgdG91dCBsZSBjb250cmFpcmUgZGVzIG1vZMOobGVzIGxpbsOpYWlyZXMgIQ0KDQpgYGB7ciBSZXN1bHRhdHMyfQ0KIyBNb3llbm5lIGRlcyByw6lzdWx0YXRzDQpmb3IgKGkgaW4gMjoxMykgew0KICBhY2N1cmFjeVsxMywgaV0gPC0gbWVhbihhY2N1cmFjeVsxOjYsIGldKQ0KICBhY2N1cmFjeVsxNCwgaV0gPC0gbWVhbihhY2N1cmFjeVs3OjksIGldKQ0KICBhY2N1cmFjeVsxNSwgaV0gPC0gbWVhbihhY2N1cmFjeVsxMDoxMiwgaV0pDQogIGFjY3VyYWN5WzE2LCBpXSA8LSBtZWFuKGFjY3VyYWN5WzEzOjE1LCBpXSkNCn0NCg0KIyBFbnJlZ2lzdHJlbWVudCBkZXMgc2NvcmVzDQpmd3JpdGUoYWNjdXJhY3ksICJzY29yZXMvMl9tb2RlbHMuY3N2IikNCg0KIyBBZmZpY2hhZ2UgZGVzIHLDqXN1bHRhdHMgZGFucyB1biB0YWJsZWF1IGludGVyYWN0aWYNCnRvX3ByaW50IDwtIGRhdGEudGFibGUodChhY2N1cmFjeVsxMzoxNiwgLTFdKSkgIyBQcsOpcGFyYXRpb24gZGVzIGRvbm7DqWVzIMOgIG1ldHRyZSBzdXIgdGFibGUNCmNvbG5hbWVzKHRvX3ByaW50KSA8LSBjKCIxIGNvbnRyZSAxIiwgIjEgY29udHJlIDIiLCAiMiBjb250cmUgMSIsICJNb3llbm5lIikgIyBSZW1pc2UgZGVzIG5vbXMgZGVzIGNvbG9ubmVzDQpyb3cubmFtZXModG9fcHJpbnQpIDwtIGNvbG5hbWVzKGFjY3VyYWN5KVstMV0gIyBSZW1pc2UgZGVzIG5vbXMgZGVzIGxpZ25lcw0KZGF0YXRhYmxlKHRvX3ByaW50LA0KICAgICAgICAgIGZpbHRlciA9ICJ0b3AiLCAjIEZpbHRyYWdlIGF1LWRlc3N1cyBkZSBsYSB0YWJsZQ0KICAgICAgICAgIGNsYXNzID0gImNlbGwtYm9yZGVyIHN0cmlwZSIsICMgQ1NTDQogICAgICAgICAgZXh0ZW5zaW9ucyA9IGMoIkNvbFJlb3JkZXIiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICJSb3dSZW9yZGVyIiksICMgUmVvcmRvbm5lciBtYW51ZWxsZW1lbnQgw6AgbGEgbWFpbg0KICAgICAgICAgIG9wdGlvbnMgPSBsaXN0KHBhZ2VMZW5ndGggPSAxMiwgIyBQYWdlIGFmZmljaGFudCAxMiBsaWduZXMNCiAgICAgICAgICAgICAgICAgICAgICAgICBvcmRlciA9IGxpc3QobGlzdCg0LCAiZGVzYyIpKSwgIyBPcmRvbm5lciBwYXIgZMOpZmF1dCBwYXIgbCdleGFjdGl0dWRlIG1veWVubmUNCiAgICAgICAgICAgICAgICAgICAgICAgICBjb2xSZW9yZGVyID0gVFJVRSwgIyBQbHVnaW4NCiAgICAgICAgICAgICAgICAgICAgICAgICByb3dSZW9yZGVyID0gVFJVRSkpICU+JSAjIFBsdWdpbg0KICBmb3JtYXRTdHlsZShjKCIxIGNvbnRyZSAxIiwgIjEgY29udHJlIDIiLCAiMiBjb250cmUgMSIpLA0KICAgICAgICAgICAgICAgICAgYmFja2dyb3VuZCA9IHN0eWxlQ29sb3JCYXIoYygwLCAxKSwgJ2xpZ2h0Z3JlZW4nKSwgIyBDb3VsZXVyIHZlcnQgY2xhaXIgcG91ciBsZXMgbcOpdHJpcXVlcyBwYXIgZm9sZA0KICAgICAgICAgICAgICAgICAgYmFja2dyb3VuZFNpemUgPSAnMTAwJSA5MCUnLA0KICAgICAgICAgICAgICAgICAgYmFja2dyb3VuZFJlcGVhdCA9ICduby1yZXBlYXQnLA0KICAgICAgICAgICAgICAgICAgYmFja2dyb3VuZFBvc2l0aW9uID0gJ2NlbnRlcicpICU+JQ0KICBmb3JtYXRTdHlsZSgiTW95ZW5uZSIsDQogICAgICAgICAgICAgIGJhY2tncm91bmQgPSBzdHlsZUNvbG9yQmFyKGMoMCwgMSksICdwaW5rJyksICMgQ291bGV1ciByb3NlIHBvdXIgbGEgbcOpdHJpcXVlIGRlIG1veWVubmUNCiAgICAgICAgICAgICAgYmFja2dyb3VuZFNpemUgPSAnMTAwJSA5MCUnLA0KICAgICAgICAgICAgICBiYWNrZ3JvdW5kUmVwZWF0ID0gJ25vLXJlcGVhdCcsDQogICAgICAgICAgICAgIGJhY2tncm91bmRQb3NpdGlvbiA9ICdjZW50ZXInKSAlPiUNCiAgZm9ybWF0UGVyY2VudGFnZShjb2x1bW5zID0gYygiMSBjb250cmUgMSIsICIxIGNvbnRyZSAyIiwgIjIgY29udHJlIDEiKSwNCiAgICAgICAgICAgICAgZGlnaXRzID0gOCkgJT4lDQogIGZvcm1hdFBlcmNlbnRhZ2UoY29sdW1ucyA9ICJNb3llbm5lIiwNCiAgICAgICAgICAgICAgZGlnaXRzID0gOCkNCg0KIyBBZmZpY2hhZ2UgZGVzIHLDqXN1bHRhdHMgZGFucyB1biB0YWJsZWF1IHN0YXRpcXVlDQpmb3JtYXR0YWJsZShhY2N1cmFjeVssIGMoMSwgMjo1KV0sIGxpc3QoZm9ybWF0dGFibGU6OmFyZWEoY29sID0geGdiX0xpbmVhck1vZGVsOnhnYl9HcmFkaWVudEJvb3N0aW5nKSB+IGNvbG9yX2Jhcigib3JhbmdlIikpKQ0KZm9ybWF0dGFibGUoYWNjdXJhY3lbLCBjKDEsIDY6OSldLCBsaXN0KGZvcm1hdHRhYmxlOjphcmVhKGNvbCA9IGgyb19MaW5lYXJNb2RlbDpoMm9fR3JhZGllbnRCb29zdGluZykgfiBjb2xvcl9iYXIoImN5YW4iKSkpDQpmb3JtYXR0YWJsZShhY2N1cmFjeVssIGMoMSwgMTA6MTMpXSwgbGlzdChmb3JtYXR0YWJsZTo6YXJlYShjb2wgPSBoMm9fTk5fMzJ4Nl9SZUxVOmgyb19OTl8xNngxNng2X1NvZnQpIH4gY29sb3JfYmFyKCJ5ZWxsb3ciKSkpDQpgYGANCg0KIyMgRMOpbW9uc3RyYXRpb24gZHUgcHJvYmzDqG1lIGxpbsOpYWlyZQ0KDQpJbCBlc3QgdHJhdmlhbCBkZSBkw6ltb250cmVyIHF1J3VuZSBwYXJ0aWUgZHUgcHJvYmzDqG1lIG5lIHBldXQgw6p0cmUgcsOpc29sdWUgZGUgbWFuacOocmUgbm9uLWxpbsOpYWlyZSBlbiBsJ8OpdGF0IHNhbnMgdW5lIGFuYWx5c2UgYXBwcm9mb25kaWUuIExlcyByw6lzaWR1cywgcGFyIGV4ZW1wbGUsIG9udCB1bmUgdmFsZXVyIG1veWVubmUgYmllbiBwbHVzIGdyYW5kZSBkYW5zIGxhIHNhbGxlIDMgKHBsdXMgZGlmZmljaWxlIMOgIHByw6l2b2lyKSBxdWUgbGVzIGRldXggYXV0cmVzIHNhbGxlcy4NCg0KUGFyIGV4ZW1wbGUsIGxlcyByw6lzaWR1cyBtb250cmVudCBxdWUgbGEgc2FsbGUgMSBlc3Qgw6AgZGlzc29jaWVyIGRlcyBkZXV4IGF1dHJlcyBzYWxsZXMuIENlbGEgZXN0IHbDqXJpZmlhYmxlIGVuIGVzdGltYW50IGxhIGRpZmbDqXJlbmNlIGRlcyBmZWF0dXJlcyBlbnRyZSBsYSBzYWxsZSAxIGV0IGxlcyBkZXV4IGF1dHJlcyBzYWxsZXMuIFBhciBzw6ljdXJpdMOpLCBvbiB1dGlsaXNlcmEgbGUgdGVzdCBVIGRlIE1hbm4tV2hpdG5leSDDoCBkZXV4IGJvcm5lcywgYXZlYyBjYWxjdWwgZXhhY3QgKHNhbnMgY29ycmVjdGlvbiBkZSBjb250aW51aXTDqSkuIFNpIGxhIHAtdmFsdWUgZXN0IHN1cMOpcmlldXJlIMOgIDAuMDUgKHNpIGwnb24gc3VwcG9zZSBub3RyZSBzZXVpbCBkZSBkw6ljaXNpb24gw6AgNSUgZCdlcnJldXIpLCBsZSB0ZXN0IG5lIHJlamV0dGUgcGFzIGwnaHlwb3Row6hzZSBudWxsZSAoZGlmZsOpcmVuY2UgZGVzIG3DqWRpYW5lcyDDqWdhbGUgw6AgesOpcm8pLiBEYW5zIGxlIGNhcyBjb250cmFpcmUsIGxhIGRpZmbDqXJlbmNlIGRlcyBtw6lkaWFuZXMgZXN0IHN0YXRpc3RpcXVlbWVudCBzdXDDqXJpZXVyZSDDoCAwIChsZXMgZGV1eCDDqWNoYW50aWxsb25zLCBsJ3VuZSBkYW5zIGxhIHNhbGxlIDEsIGwnYXV0cmUgZGFucyBsZXMgZGV1eCBhdXRyZXMgc2FsbGVzLCBzb250IHN0YXRpc3RpcXVlbWVudCBkaWZmw6lyZW50cykuIE9uIGFmZmljaGVyYSBsJ2ludGVydmFsbGUgZGUgY29uZmlhbmNlIMOgIDk1JSBkZXMgbcOpZGlhbmVzLg0KDQpgYGB7ciBMaW5lYXJpdGUsIGNhY2hlPVRSVUUsIHdhcm5pbmc9RkFMU0UsIGZpZy5oZWlnaHQ9OCwgZmlnLndpZHRoPTEyfQ0KIyBDb21wdGV1ciBkZSB0ZW1wcw0KQ3VycmVudFRpbWUgPC0gdGltZXIoKSAjIFByw6lwYXJhdGlvbiBkZSBsJ8OpdmFsdWF0aW9uIGR1IG1vZMOobGUgbmV0dG95w6kNCg0KIyBQcsOpLWluaXRpYWxpc2F0aW9uIGRlcyB2YXJpYWJsZXMNCnRlbXBfdXRlc3QgPC0gZGF0YS5mcmFtZShGZWF0dXJlID0gYyhwYXN0ZTAocmVwKGMocGFzdGUwKCJDb2VmIiwgMTo0KSwgcGFzdGUwKCJSw6lzaSIsIDE6NCkpLCA0KSwgcGFzdGUwKCJfIiwgaW52ZXJzZS5ybGUobGlzdChsZW5ndGhzID0gcmVwKDgsIDQpLCB2YWx1ZXMgPSAxOjQpKSkpLCBwYXN0ZTAoIlBvc0luaXRpYWxlXyIsIDE6NCkpLA0KICAgICAgICAgICAgICAgICAgICAgICAgIHBfdmFsdWUgPSBudW1lcmljKDM2KSwNCiAgICAgICAgICAgICAgICAgICAgICAgICBtZWRpYW5fZXN0ID0gbnVtZXJpYygzNiksDQogICAgICAgICAgICAgICAgICAgICAgICAgbWVkaWFuX2luZjk1ID0gbnVtZXJpYygzNiksDQogICAgICAgICAgICAgICAgICAgICAgICAgbWVkaWFuX3N1cDk1ID0gbnVtZXJpYygzNikpDQoNCiMgTWFubi1XaGl0bmV5IGV4YWN0IHR3by10YWlsZWQgVS10ZXN0DQpmb3IgKGkgaW4gMTozNikgew0KICB0ZW1wX3doaXRuZXkgPC0gd2lsY294LnRlc3QobWluaV9sbVtbaV1dW2dyb3VwX3BhdGgkcGF0aF9JRCA9PSAxXSwgbWluaV9sbVtbaV1dW2dyb3VwX3BhdGgkcGF0aF9JRCAhPSAxXSwgYWx0ZXJuYXRpdmUgPSAidHdvLnNpZGVkIiwgcGFpcmVkID0gRkFMU0UsIGV4YWN0ID0gVFJVRSwgY29uZi5pbnQgPSBUUlVFLCBjb25mLmxldmVsID0gMC45NSkNCiAgdGVtcF91dGVzdFtpLCAyOjVdIDwtIGModGVtcF93aGl0bmV5JHAudmFsdWUsIHRlbXBfd2hpdG5leSRlc3RpbWF0ZSwgdGVtcF93aGl0bmV5JGNvbmYuaW50KQ0KfQ0KDQojIER1bXAgZGVzIGRvbm7DqWVzIGRlcyB0ZXN0cyBVIMOgIGRldXggYm9ybmVzIGRlIE1hbm4tV2hpdG5leQ0KZndyaXRlKGFjY3VyYWN5LCAic3RhdHMvdV90ZXN0LmNzdiIpDQoNCiMgVGFibGVhdSBpbnRlcmFjdGlmIGR1IFUgdGVzdCBkZSBNYW5uLVdoaXRuZXkNCmRhdGF0YWJsZSh0ZW1wX3V0ZXN0LA0KICAgICAgICBmaWx0ZXIgPSAidG9wIiwgIyBGaWx0cmFnZSBhdS1kZXNzdXMgZGUgbGEgdGFibGUNCiAgICAgICAgY2xhc3MgPSAiY2VsbC1ib3JkZXIgc3RyaXBlIiwgIyBDU1MNCiAgICAgICAgZXh0ZW5zaW9ucyA9IGMoIkNvbFJlb3JkZXIiLA0KICAgICAgICAgICAgICAgICAgICAgICAiUm93UmVvcmRlciIpLCAjIFJlb3Jkb25uZXIgbWFudWVsbGVtZW50IMOgIGxhIG1haW4NCiAgICAgICAgb3B0aW9ucyA9IGxpc3Qob3JkZXIgPSBsaXN0KGxpc3QoMiwgImRlc2MiKSksICMgT3Jkb25uZXIgcGFyIGTDqWZhdXQgcGFyIGxlcyBmYWN0ZXVycyBheWFudCBsYSBwLnZhbHVlIGxhIHBsdXMgZ3JhbmRlDQogICAgICAgICAgICAgICAgICAgICAgIGNvbFJlb3JkZXIgPSBUUlVFLCAjIFBsdWdpbg0KICAgICAgICAgICAgICAgICAgICAgICByb3dSZW9yZGVyID0gVFJVRSkpICU+JSAjIFBsdWdpbg0KICBmb3JtYXRTdHlsZSgicF92YWx1ZSIsDQogICAgICAgICAgICAgICAgICBiYWNrZ3JvdW5kID0gc3R5bGVDb2xvckJhcihjKDAsIDEpLCAnbGlnaHRibHVlJyksICMgQ291bGV1ciBibGV1ZSBwb3VyIGxlIGNvZWZmaWNpZW50DQogICAgICAgICAgICAgICAgICBiYWNrZ3JvdW5kU2l6ZSA9ICcxMDAlIDkwJScsDQogICAgICAgICAgICAgICAgICBiYWNrZ3JvdW5kUmVwZWF0ID0gJ25vLXJlcGVhdCcsDQogICAgICAgICAgICAgICAgICBiYWNrZ3JvdW5kUG9zaXRpb24gPSAnY2VudGVyJykgJT4lDQogIGZvcm1hdFN0eWxlKGMoIm1lZGlhbl9lc3QiLCAibWVkaWFuX2luZjk1IiwgIm1lZGlhbl9zdXA5NSIpLA0KICAgICAgICAgICAgICBiYWNrZ3JvdW5kID0gc3R5bGVDb2xvckJhcihyYW5nZSh0ZW1wX3V0ZXN0WywgMzo1XSksICdwaW5rJyksICMgQ291bGV1ciByb3NlIHBvdXIgbGEgZGlmZsOpcmVuY2UgZGUgbcOpZGlhbmUgZXN0aW3DqWUNCiAgICAgICAgICAgICAgYmFja2dyb3VuZFNpemUgPSAnMTAwJSA5MCUnLA0KICAgICAgICAgICAgICBiYWNrZ3JvdW5kUmVwZWF0ID0gJ25vLXJlcGVhdCcsDQogICAgICAgICAgICAgIGJhY2tncm91bmRQb3NpdGlvbiA9ICdjZW50ZXInKSAlPiUNCiAgZm9ybWF0Um91bmQoY29sdW1ucyA9IGMoInBfdmFsdWUiLCAibWVkaWFuX2VzdCIsICJtZWRpYW5faW5mOTUiLCAibWVkaWFuX3N1cDk1IiksDQogICAgICAgICAgICAgIGRpZ2l0cyA9IDYpDQoNCiMgQ3LDqWF0aW9uIGRlIGxhIHRhYmxlIHBvdXIgbGEgdmlzdWFsaXNhdGlvbg0KdGVtcF9sbSA8LSBjb3B5KG1pbmlfbG0pDQpjb2xuYW1lcyh0ZW1wX2xtKSA8LSBjKHBhc3RlMChyZXAoYyhwYXN0ZTAoIkNvZWYiLCAxOjQpLCBwYXN0ZTAoIlLDqXNpIiwgMTo0KSksIDQpLCBwYXN0ZTAoIl8iLCBpbnZlcnNlLnJsZShsaXN0KGxlbmd0aHMgPSByZXAoOCwgNCksIHZhbHVlcyA9IDE6NCkpKSksIHBhc3RlMCgiUG9zSW5pdGlhbGVfIiwgMTo0KSkNCg0KdGltaW5nKEN1cnJlbnRUaW1lLCAiQ2FsY3VscyBzdGF0aXN0aXF1ZXMiKQ0KDQojIFRhYmxlcGxvdCBkZXMgY29lZmZpY2llbnRzLCB0cmFqZWN0b2lyZXMsIGV0IHNhbGxlcw0KcGxvdCh0YWJsZXBsb3QoZGF0ID0gY2JpbmQoVHJhamVjdG9pcmUgPSBhcy5mYWN0b3IoZ3JvdXBfcGF0aCRwYXRoX0lEKSwgU2FsbGUgPSBhcy5mYWN0b3IoZ3JvdXBfcm9vbSRkYXRhc2V0X0lEKSwgdGVtcF9sbVssIGMoMTo0LCA5OjEyLCAxNzoyMCwgMjU6MjgpXSksIHNvcnRDb2wgPSAyLCBuQmlucyA9IDIwLCBzY2FsZXMgPSAibGluIiwgcGxvdCA9IEZBTFNFKSwgdGl0bGUgPSAiVHJhamVjdG9pcmUgdnMgQ29lZmZpY2llbnRzIikNCg0KIyBUYWJsZXBsb3QgZGVzIHLDqXNpZHVzLCB0cmFqZWN0b2lyZXMsIGV0IHNhbGxlcw0KcGxvdCh0YWJsZXBsb3QoZGF0ID0gY2JpbmQoVHJhamVjdG9pcmUgPSBhcy5mYWN0b3IoZ3JvdXBfcGF0aCRwYXRoX0lEKSwgU2FsbGUgPSBhcy5mYWN0b3IoZ3JvdXBfcm9vbSRkYXRhc2V0X0lEKSwgdGVtcF9sbVssIGMoNTo4LCAxMzoxNiwgMjE6MjQsIDI5OjMyKV0pLCBzb3J0Q29sID0gMiwgbkJpbnMgPSAyMCwgc2NhbGVzID0gImxpbiIsIHBsb3QgPSBGQUxTRSksIHRpdGxlID0gIlRyYWplY3RvaXJlIHZzIFLDqXNpZHVzIikNCg0KIyBUYWJsZXBsb3QgZGVzIHBvc2l0aW9ucyBpbml0aWFsZXMsIHRyYWplY3RvaXJlcywgZXQgc2FsbGVzDQpwbG90KHRhYmxlcGxvdChkYXQgPSBjYmluZChUcmFqZWN0b2lyZSA9IGFzLmZhY3Rvcihncm91cF9wYXRoJHBhdGhfSUQpLCBTYWxsZSA9IGFzLmZhY3Rvcihncm91cF9yb29tJGRhdGFzZXRfSUQpLCB0ZW1wX2xtWywgYygzMzozNildKSwgc29ydENvbCA9IDIsIG5CaW5zID0gMjAsIHNjYWxlcyA9ICJsaW4iLCBwbG90ID0gRkFMU0UpLCB0aXRsZSA9ICJUcmFqZWN0b2lyZSB2cyBQb3NpdGlvbiBJbml0aWFsZSIpDQpgYGANCg0KIyMgQW5hbHlzZSBkdSBtb2TDqGxlIGRlIHLDqWdyZXNzaW9uIGxvZ2lzdGlxdWUNCg0KUG91ciBhbmFseXNlciBsZSBtb2TDqGxlIGRlIHLDqWdyZXNzaW9uIGxvZ2lzdGlxdWUsIG5vdXMgYWxsb25zIHV0aWxpc2VyIGxlIG1vZMOobGUgbGluw6lhaXJlIHhnYm9vc3QgcXVpIGVzdCB1biBtb2TDqGxlIHRyw6hzIHNpbXBsZSAow6lxdWl2YWxlbnQgZCd1bmUgKiJyw6lncmVzc2lvbiBsaW7DqWFpcmUgYXZlYyB1bmUgYXBwbGljYXRpb24gc2lnbW/Dr2RhbGUgKFNvZnRtYXgpIiopLiBJbCByZXN0ZSBjbGFpciBxdWUgbGEgc2FsbGUgMSBkb25uZSB0b3Vqb3VycyBkZXMgcHJvYmzDqG1lcywgbWFpcyBsZXMgcsOpc3VsdGF0cyBzb250IGTDqWrDoCBiaWVuIG1laWxsZXVycyBhdmVjIHVuZSBleGFjdGl0dWRlIGQnZW52aXJvbiA1NyUhIChxdWkgZXN0IG5ldHRlbWVudCBzdXDDqXJpZXVyZSBhdSBtb2TDqGxlIGFsw6lhdG9pcmUsIGRldmFudCBhdHRlaW5kcmUgdW5pcXVlbWVudCBgciBzcHJpbnRmKCIlMDUuMDJmIiwgMTAwICogbWF4KHRhYnVsYXRlKGdyb3VwX3BhdGgkcGF0aF9JRFtncm91cF9yb29tJGRhdGFzZXRfSUQgPT0gMV0pKSAvIHN1bSh0YWJ1bGF0ZShncm91cF9wYXRoJHBhdGhfSURbZ3JvdXBfcm9vbSRkYXRhc2V0X0lEID09IDFdKSkpYCUgZGUgcHLDqWNpc2lvbikuDQoNClBvdXIgw6l2aXRlciB0b3V0ZSBjb25mdXNpb24sIG5vdXMgdXRpbGlzZXJvbnMgbGEgc8OpbWFudGlxdWUgc3VpdmFudGUgOiBGb2xkID0gU2FsbGUuIERlIHBsdXMsIG5vdXMgdXRpbGlzZXJvbnMgbGVzIHZhbGV1cnMgYWJzb2x1ZXMgYWZpbiBkZSBuZSBwYXMgaW1wYWN0ZXIgbGVzIG5vbWJyZXMgdmVycyB6w6lyb3MgKHMnaWxzIHBhcmFpc3NlbnQgw6AgbGEgZm9pcyBuw6lnYXRpZnMgZXQgcG9zaXRpZnMsIHBvdXIgZGlmZsOpcmVudHMgZm9sZHMpLiBMZXMgc2lnbmVzIHNvbnQgZG9ubsOpcyBzw6lwYXLDqW1lbnQuDQoNCkxhIG1hdHJpY2UgZGUgY29uZnVzaW9uIG5vdXMgbW9udHJlIGNsYWlyZWVtbnQgcXVlIGxlcyBjaGVtaW5zIDEgZXQgMiBzb250IHByb2Jsw6ltYXRpcXVlcyA6IGlscyBzZSBtw6lsYW5nZW50LiBEZSBtw6ptZSBwb3VyIGxhIHNhbGxlIDMsIGRvbnQgbCdleGFjdGl0dWRlIGRlIGxhIHByw6lkaWN0aW9uIG4nZXN0IHF1ZSBkZSAxMiUuDQoNCmBgYHtyIEFuYWx5c2VMb2dpc3RpcXVlMSwgd2FybmluZ3MgPSBGQUxTRX0NCiMgQ29tcHRldXIgZGUgdGVtcHMNCkN1cnJlbnRUaW1lIDwtIHRpbWVyKCkgIyBDaHVuayBQcsOpcGFyYXRpb24gZGUgbCdhbmFseXNlIGR1IG1vZMOobGUgZGUgcsOpZ3Jlc3Npb24gbG9naXN0aXF1ZQ0KDQojIFByw6ktaW5pdGlhbGlzYXRpb24gZGVzIHZhcmlhYmxlcw0KcHJlZGljdGVkVmFsdWVzIDwtIG1hdHJpeChucm93ID0gMzE0LCBuY29sID0gNikNCmV2b2x1dGlvbiA8LSBsaXN0KCkNCnRlbXBfZHQgPC0gbGlzdCgpDQp0ZW1wX21lYW5zIDwtIGRhdGEuZnJhbWUoRmVhdHVyZSA9IGMocGFzdGUwKHJlcChjKHBhc3RlMCgiQ29lZiIsIDE6NCksIHBhc3RlMCgiUsOpc2kiLCAxOjQpKSwgNCksIHBhc3RlMCgiXyIsIGludmVyc2UucmxlKGxpc3QobGVuZ3RocyA9IHJlcCg4LCA0KSwgdmFsdWVzID0gMTo0KSkpKSwgcGFzdGUwKCJQb3NJbml0aWFsZV8iLCAxOjQpKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICBGb2xkXzEgPSBudW1lcmljKDM2KSwNCiAgICAgICAgICAgICAgICAgICAgICAgICBGb2xkXzIgPSBudW1lcmljKDM2KSwNCiAgICAgICAgICAgICAgICAgICAgICAgICBGb2xkXzMgPSBudW1lcmljKDM2KSwNCiAgICAgICAgICAgICAgICAgICAgICAgICBGb2xkX01lYW4gPSBudW1lcmljKDM2KSwNCiAgICAgICAgICAgICAgICAgICAgICAgICBGZWF0dXJlX01lYW4gPSBudW1lcmljKDM2KSwNCiAgICAgICAgICAgICAgICAgICAgICAgICBGZWF0dXJlX1NEID0gbnVtZXJpYygzNikpDQoNCiMgQm91Y2xlIGQnZW50cmFpbmVtZW50IDIgY29udHJlIDENCmZvciAoaSBpbiAxMDoxMikgew0KICANCiAgIyBFbnRyYWluZW1lbnQgZCd1biBtb2TDqGxlIGxpbsOpYWlyZQ0KICB0ZW1wX21vZGVsIDwtIHhnYi50cmFpbihkYXRhID0gdHJhaW5pbmdfeGdiW1tpXV0sDQogICAgICAgICAgICAgICAgICAgICAgICAgIG51bV9jbGFzcyA9IDYsICMgQ2xhc3NpZmljYXRpb24gw6AgNiBjbGFzc2VzDQogICAgICAgICAgICAgICAgICAgICAgICAgIG50aHJlYWQgPSAxLCAjIDEgY29ldXIgdXRpbGlzw6kNCiAgICAgICAgICAgICAgICAgICAgICAgICAgbnJvdW5kcyA9IDEwMDAwMDAsICMgTm9tYnJlIGQnaXTDqXJhdGlvbnMgZGUgYm9vc3RpbmcNCiAgICAgICAgICAgICAgICAgICAgICAgICAgZXRhID0gMC4xMCwgIyBTaHJpbmthZ2UgcG91ciBsZSBib29zdGluZw0KICAgICAgICAgICAgICAgICAgICAgICAgICBib29zdGVyID0gImdibGluZWFyIiwgIyBUeXBlIGQnZW50cmFpbmVtZW50IDogbGluw6lhaXJlIG91IG5vbi1saW7DqWFpcmUNCiAgICAgICAgICAgICAgICAgICAgICAgICAgb2JqZWN0aXZlID0gIm11bHRpOnNvZnRwcm9iIiwgIyBHcmFkaWVudC9IZXNzaWFuIHBvdXIgbCdvcHRpbWlzYXRpb24gcGFyIEdyYWRpZW50IERlc2NlbnQNCiAgICAgICAgICAgICAgICAgICAgICAgICAgZXZhbF9tZXRyaWMgPSAibWVycm9yIiwgIyBJbmV4YWN0aXR1ZGUgZGUgbGEgY2xhc3NpZmljYXRpb24NCiAgICAgICAgICAgICAgICAgICAgICAgICAgbWF4aW1pemUgPSBGQUxTRSwgIyBNaW5pbWlzYXRpb24gZGUgbCdlcnJldXINCiAgICAgICAgICAgICAgICAgICAgICAgICAgZWFybHlfc3RvcHBpbmdfcm91bmRzID0gMTAwLCAjIEFycsOqdCBhcHLDqHMgMTAwIGl0w6lyYXRpb25zIHNhbnMgYW3DqWxpb3JhdGlvbiBkZSBsYSBtw6l0cmlxdWUNCiAgICAgICAgICAgICAgICAgICAgICAgICAgdmVyYm9zZSA9IEZBTFNFLCAjIFNhbnMgcHJpbnQgZGVzIGl0w6lyYXRpb25zDQogICAgICAgICAgICAgICAgICAgICAgICAgIHdhdGNobGlzdCA9IGxpc3QodGVzdCA9IHRlc3RpbmdfeGdiW1tpXV0pLCAjIEVzdGltYXRpb24gc3VyIGxlcyBkb25uw6llcyBkZSB0ZXN0DQogICAgICAgICAgICAgICAgICAgICAgICAgIGNhbGxiYWNrcyA9IGxpc3QoY2IuZXZhbHVhdGlvbi5sb2coKSkpICMgTG9nZ2luZyBkZXMgZG9ubsOpZXMgZCdlbnRyYWluZW1lbnQgcG91ciBwb3V2b2lyIHLDqWN1cMOpcmVyIGxlcyBtw6l0cmlxdWVzDQogIA0KICAjIEVucmVnaXN0cmVtZW50IGR1IGxvZw0KICBldm9sdXRpb25bW2kgLSA5XV0gPC0gY2JpbmQodGVtcF9tb2RlbCRldmFsdWF0aW9uX2xvZywgRm9sZCA9IHJlcCgxMyAtIGksIHRlbXBfbW9kZWwkbml0ZXIpKQ0KICANCiAgIyBFbnRyYWluZW1lbnQgZHUgbWVpbGxldXIgbW9kw6hsZSAob2J0ZW50aW9uIGRlcyBtZWlsbGV1cnMgY29lZmZpY2llbnRzKQ0KICB0ZW1wX21vZGVsIDwtIHhnYi50cmFpbihkYXRhID0gdHJhaW5pbmdfeGdiW1tpXV0sDQogICAgICAgICAgICAgICAgICAgICAgICAgIG51bV9jbGFzcyA9IDYsICMgQ2xhc3NpZmljYXRpb24gw6AgNiBjbGFzc2VzDQogICAgICAgICAgICAgICAgICAgICAgICAgIG50aHJlYWQgPSAxLCAjIDEgY29ldXIgdXRpbGlzw6kNCiAgICAgICAgICAgICAgICAgICAgICAgICAgbnJvdW5kcyA9IHRlbXBfbW9kZWwkYmVzdF9pdGVyYXRpb24sICMgTm9tYnJlIGQnaXTDqXJhdGlvbnMgZGUgYm9vc3RpbmcNCiAgICAgICAgICAgICAgICAgICAgICAgICAgZXRhID0gMC4xMCwgIyBTaHJpbmthZ2UgcG91ciBsZSBib29zdGluZw0KICAgICAgICAgICAgICAgICAgICAgICAgICBib29zdGVyID0gImdibGluZWFyIiwgIyBUeXBlIGQnZW50cmFpbmVtZW50IDogbGluw6lhaXJlIG91IG5vbi1saW7DqWFpcmUNCiAgICAgICAgICAgICAgICAgICAgICAgICAgb2JqZWN0aXZlID0gIm11bHRpOnNvZnRwcm9iIiwgIyBHcmFkaWVudC9IZXNzaWFuIHBvdXIgbCdvcHRpbWlzYXRpb24gcGFyIEdyYWRpZW50IERlc2NlbnQNCiAgICAgICAgICAgICAgICAgICAgICAgICAgZXZhbF9tZXRyaWMgPSAibWVycm9yIiwgIyBJbmV4YWN0aXR1ZGUgZGUgbGEgY2xhc3NpZmljYXRpb24NCiAgICAgICAgICAgICAgICAgICAgICAgICAgbWF4aW1pemUgPSBGQUxTRSwgIyBNaW5pbWlzYXRpb24gZGUgbCdlcnJldXINCiAgICAgICAgICAgICAgICAgICAgICAgICAgZWFybHlfc3RvcHBpbmdfcm91bmRzID0gOTk5OTksICMgU2FucyBhcnLDqnQNCiAgICAgICAgICAgICAgICAgICAgICAgICAgdmVyYm9zZSA9IEZBTFNFLCAjIFNhbnMgcHJpbnQgZGVzIGl0w6lyYXRpb25zDQogICAgICAgICAgICAgICAgICAgICAgICAgIHdhdGNobGlzdCA9IGxpc3QodGVzdCA9IHRlc3RpbmdfeGdiW1tpXV0pLCAjIEVzdGltYXRpb24gc3VyIGxlcyBkb25uw6llcyBkZSB0ZXN0DQogICAgICAgICAgICAgICAgICAgICAgICAgIGNhbGxiYWNrcyA9IGxpc3QoY2IuZXZhbHVhdGlvbi5sb2coKSkpICMgTG9nZ2luZyBkZXMgZG9ubsOpZXMgZCdlbnRyYWluZW1lbnQgcG91ciBwb3V2b2lyIHLDqWN1cMOpcmVyIGxlcyBtw6l0cmlxdWVzDQogIA0KICAjIFByw6lkaWN0aW9uIGR1IG1vZMOobGUgbGluw6lhaXJlDQogIHByZWRpY3RlZFZhbHVlc1tmb2xkc190ZXN0W1tpXV0sIF0gPC0gdChtYXRyaXgocHJlZGljdCh0ZW1wX21vZGVsLCB0ZXN0aW5nX3hnYltbaV1dLCBudHJlZWxpbWl0ID0gMCksIG5yb3cgPSA2KSkNCiAgDQogICMgQ2FsY3VsIGV0IGZvcm1hdHRhZ2UgZGUgbCdpbXBvcnRhbmNlIGRlcyB2YXJpYWJsZXMNCiAgdGVtcF9pbXBvcnRhbmNlIDwtIGRhdGEudGFibGUoRmVhdHVyZSA9IHRlbXBfbWVhbnNbWyJGZWF0dXJlIl1dLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtYXRyaXgoeGdiLmltcG9ydGFuY2UobW9kZWwgPSB0ZW1wX21vZGVsKSRXZWlnaHQsIG5jb2wgPSA2KSkNCiAgY29sbmFtZXModGVtcF9pbXBvcnRhbmNlKSA8LSBjKCJGZWF0dXJlIiwgcGFzdGUwKCJMYWJlbF8iLCAxOjYpKQ0KICB0ZW1wX2ltcG9ydGFuY2VbWyJTaWduIl1dIDwtIHBhc3RlMChpZmVsc2UodGVtcF9pbXBvcnRhbmNlW1syXV0gPj0gMCwgIisiLCAiLSIpLCBpZmVsc2UodGVtcF9pbXBvcnRhbmNlW1szXV0gPj0gMCwgIisiLCAiLSIpLCBpZmVsc2UodGVtcF9pbXBvcnRhbmNlW1s0XV0gPj0gMCwgIisiLCAiLSIpLCBpZmVsc2UodGVtcF9pbXBvcnRhbmNlW1s1XV0gPj0gMCwgIisiLCAiLSIpLCBpZmVsc2UodGVtcF9pbXBvcnRhbmNlW1s2XV0gPj0gMCwgIisiLCAiLSIpLCBpZmVsc2UodGVtcF9pbXBvcnRhbmNlW1s3XV0gPj0gMCwgIisiLCAiLSIpKQ0KICB0ZW1wX2ltcG9ydGFuY2VbLCAyOjddIDwtIGFicyh0ZW1wX2ltcG9ydGFuY2VbLCAyOjcsIHdpdGggPSBGQUxTRV0pIA0KICB0ZW1wX21lYW5zW1sxNCAtIGldXSA8LSByb3dNZWFucyh0ZW1wX2ltcG9ydGFuY2VbLCAyOjcsIHdpdGggPSBGQUxTRV0pDQogIHRlbXBfaW1wb3J0YW5jZVtbcGFzdGUwKCJGb2xkXyIsIDEzIC0gaSwgIl9NZWFuIildXSA8LSB0ZW1wX21lYW5zW1sxNCAtIGldXQ0KICANCiAgIyBFbnJlZ2lzdHJlbWVudCBzb3VzIGZvcm1lIGRlIHRhYmxlYXUgaW50ZXJhY3RpZg0KICB0ZW1wX2R0W1tpIC0gOV1dIDwtIGRhdGF0YWJsZSh0ZW1wX2ltcG9ydGFuY2UsDQogICAgICAgIGZpbHRlciA9ICJ0b3AiLCAjIEZpbHRyYWdlIGF1LWRlc3N1cyBkZSBsYSB0YWJsZQ0KICAgICAgICBjbGFzcyA9ICJjZWxsLWJvcmRlciBzdHJpcGUiLCAjIENTUw0KICAgICAgICBleHRlbnNpb25zID0gYygiQ29sUmVvcmRlciIsDQogICAgICAgICAgICAgICAgICAgICAgICJSb3dSZW9yZGVyIiksICMgUmVvcmRvbm5lciBtYW51ZWxsZW1lbnQgw6AgbGEgbWFpbg0KICAgICAgICBvcHRpb25zID0gbGlzdChvcmRlciA9IGxpc3QobGlzdCg5LCAiZGVzYyIpKSwgIyBPcmRvbm5lciBwYXIgZMOpZmF1dCBwYXIgbGVzIGZhY3RldXJzIGF5YW50IGxlIHBvaWRzIGxlIHBsdXMgZ3Jvcw0KICAgICAgICAgICAgICAgICAgICAgICBjb2xSZW9yZGVyID0gVFJVRSwgIyBQbHVnaW4NCiAgICAgICAgICAgICAgICAgICAgICAgcm93UmVvcmRlciA9IFRSVUUpKSAlPiUgIyBQbHVnaW4NCiAgZm9ybWF0U3R5bGUocGFzdGUwKCJMYWJlbF8iLCAxOjYpLA0KICAgICAgICAgICAgICAgICAgYmFja2dyb3VuZCA9IHN0eWxlQ29sb3JCYXIocmFuZ2UodGVtcF9pbXBvcnRhbmNlWywgMjo3LCB3aXRoID0gRkFMU0VdKSwgJ2xpZ2h0Ymx1ZScpLCAjIENvdWxldXIgYmxldWUgcG91ciBsZSBjb2VmZmljaWVudA0KICAgICAgICAgICAgICAgICAgYmFja2dyb3VuZFNpemUgPSAnMTAwJSA5MCUnLA0KICAgICAgICAgICAgICAgICAgYmFja2dyb3VuZFJlcGVhdCA9ICduby1yZXBlYXQnLA0KICAgICAgICAgICAgICAgICAgYmFja2dyb3VuZFBvc2l0aW9uID0gJ2NlbnRlcicpICU+JQ0KICBmb3JtYXRTdHlsZShwYXN0ZTAoIkZvbGRfIiwgMTMgLSBpLCAiX01lYW4iKSwNCiAgICAgICAgICAgICAgYmFja2dyb3VuZCA9IHN0eWxlQ29sb3JCYXIocmFuZ2UodGVtcF9pbXBvcnRhbmNlW1s5XV0pLCAncGluaycpLCAjIENvdWxldXIgcm9zZSBwb3VyIGxhIG3DqXRyaXF1ZSBkZSBtb3llbm5lDQogICAgICAgICAgICAgIGJhY2tncm91bmRTaXplID0gJzEwMCUgOTAlJywNCiAgICAgICAgICAgICAgYmFja2dyb3VuZFJlcGVhdCA9ICduby1yZXBlYXQnLA0KICAgICAgICAgICAgICBiYWNrZ3JvdW5kUG9zaXRpb24gPSAnY2VudGVyJykgJT4lDQogIGZvcm1hdFJvdW5kKGNvbHVtbnMgPSBjKHBhc3RlMCgiTGFiZWxfIiwgMTo2KSwgcGFzdGUwKCJGb2xkXyIsIDEzIC0gaSwgIl9NZWFuIikpLA0KICAgICAgICAgICAgICBkaWdpdHMgPSA2KQ0KICANCn0NCg0KIyBDYWxjdWwgZHUgcG9pZHMgbW95ZW4gYWZmZWN0w6kgw6AgY2hhcXVlIGZlYXR1cmUNCnRlbXBfbWVhbnNbWzVdXSA8LSByb3dNZWFucyh0ZW1wX21lYW5zWywgMjo0XSkgIyBQb2lkcyBtb3llbg0KdGVtcF9tZWFuc1tbNl1dIDwtIGFwcGx5KG1pbmlfbG0sIDIsIGZ1bmN0aW9uKHgpIHttZWFuKHgpfSkgIyBNb3llbm5lIGRlIGxhIGZlYXR1cmUgZGFucyBsZXMgZG9ubsOpZXMNCnRlbXBfbWVhbnNbWzddXSA8LSBhcHBseShtaW5pX2xtLCAyLCBmdW5jdGlvbih4KSB7c2QoeCl9KSAjIEVjYXJ0LXR5cGUgZGUgbGEgZmVhdHVyZSBkYW5zIGxlcyBkb25uw6llcw0KDQojIFByw6lwcmF0aW9uIGR1IHRhYmxlYXUgaW50ZXJhY3RpZiBzdXIgbGVzIHBvaWRzIG1veWVucyBhZ3LDqWfDqXMNCnRlbXBfZHRbWzRdXSA8LSBkYXRhdGFibGUodGVtcF9tZWFucywNCiAgICAgICAgZmlsdGVyID0gInRvcCIsICMgRmlsdHJhZ2UgYXUtZGVzc3VzIGRlIGxhIHRhYmxlDQogICAgICAgIGNsYXNzID0gImNlbGwtYm9yZGVyIHN0cmlwZSIsICMgQ1NTDQogICAgICAgIGV4dGVuc2lvbnMgPSBjKCJDb2xSZW9yZGVyIiwNCiAgICAgICAgICAgICAgICAgICAgICAgIlJvd1Jlb3JkZXIiKSwgIyBSZW9yZG9ubmVyIG1hbnVlbGxlbWVudCDDoCBsYSBtYWluDQogICAgICAgIG9wdGlvbnMgPSBsaXN0KG9yZGVyID0gbGlzdChsaXN0KDUsICJkZXNjIikpLCAjIE9yZG9ubmVyIHBhciBkw6lmYXV0IHBhciBsZXMgZmFjdGV1cnMgYXlhbnQgbGUgcG9pZHMgbGUgcGx1cyBncm9zIGVuIG1veWVubmUNCiAgICAgICAgICAgICAgICAgICAgICAgY29sUmVvcmRlciA9IFRSVUUsICMgUGx1Z2luDQogICAgICAgICAgICAgICAgICAgICAgIHJvd1Jlb3JkZXIgPSBUUlVFKSkgJT4lICMgUGx1Z2luDQogIGZvcm1hdFN0eWxlKHBhc3RlMCgiRm9sZF8iLCAxOjMpLA0KICAgICAgICAgICAgICAgICAgYmFja2dyb3VuZCA9IHN0eWxlQ29sb3JCYXIocmFuZ2UodGVtcF9tZWFuc1ssIDI6NF0pLCAnbGlnaHRibHVlJyksICMgQ291bGV1ciBibGV1ZSBwb3VyIGxlIGNvZWZmaWNpZW50DQogICAgICAgICAgICAgICAgICBiYWNrZ3JvdW5kU2l6ZSA9ICcxMDAlIDkwJScsDQogICAgICAgICAgICAgICAgICBiYWNrZ3JvdW5kUmVwZWF0ID0gJ25vLXJlcGVhdCcsDQogICAgICAgICAgICAgICAgICBiYWNrZ3JvdW5kUG9zaXRpb24gPSAnY2VudGVyJykgJT4lDQogIGZvcm1hdFN0eWxlKCJGb2xkX01lYW4iLA0KICAgICAgICAgICAgICBiYWNrZ3JvdW5kID0gc3R5bGVDb2xvckJhcihyYW5nZSh0ZW1wX21lYW5zW1s1XV0pLCAncGluaycpLCAjIENvdWxldXIgcm9zZSBwb3VyIGxhIG3DqXRyaXF1ZSBkZSBtb3llbm5lDQogICAgICAgICAgICAgIGJhY2tncm91bmRTaXplID0gJzEwMCUgOTAlJywNCiAgICAgICAgICAgICAgYmFja2dyb3VuZFJlcGVhdCA9ICduby1yZXBlYXQnLA0KICAgICAgICAgICAgICBiYWNrZ3JvdW5kUG9zaXRpb24gPSAnY2VudGVyJykgJT4lDQogIGZvcm1hdFN0eWxlKCJGZWF0dXJlX01lYW4iLA0KICAgICAgICAgICAgICBiYWNrZ3JvdW5kID0gc3R5bGVDb2xvckJhcihyYW5nZSh0ZW1wX21lYW5zW1s2XV0pLCAnbGlnaHRncmVlbicpLCAjIENvdWxldXIgdmVydGUgcG91ciBsYSBtb3llbm5lIGRlcyBmZWF0dXJlcw0KICAgICAgICAgICAgICBiYWNrZ3JvdW5kU2l6ZSA9ICcxMDAlIDkwJScsDQogICAgICAgICAgICAgIGJhY2tncm91bmRSZXBlYXQgPSAnbm8tcmVwZWF0JywNCiAgICAgICAgICAgICAgYmFja2dyb3VuZFBvc2l0aW9uID0gJ2NlbnRlcicpICU+JQ0KICBmb3JtYXRTdHlsZSgiRmVhdHVyZV9TRCIsDQogICAgICAgICAgICAgIGJhY2tncm91bmQgPSBzdHlsZUNvbG9yQmFyKHJhbmdlKHRlbXBfbWVhbnNbWzddXSksICdvcmFuZ2UnKSwgIyBDb3VsZXVyIHZlcnRlIHBvdXIgbCfDqWNhcnQtdHlwZSBkZXMgZmVhdHVyZXMNCiAgICAgICAgICAgICAgYmFja2dyb3VuZFNpemUgPSAnMTAwJSA5MCUnLA0KICAgICAgICAgICAgICBiYWNrZ3JvdW5kUmVwZWF0ID0gJ25vLXJlcGVhdCcsDQogICAgICAgICAgICAgIGJhY2tncm91bmRQb3NpdGlvbiA9ICdjZW50ZXInKSAlPiUNCiAgZm9ybWF0Um91bmQoY29sdW1ucyA9IGMocGFzdGUwKCJGb2xkXyIsIDE6MyksICJGb2xkX01lYW4iLCAiRmVhdHVyZV9NZWFuIiwgIkZlYXR1cmVfU0QiKSwNCiAgICAgICAgICAgICAgZGlnaXRzID0gNikNCg0KIyBEw6lwaXZvdGFnZSBkdSBsb2cNCmV2b2x1dGlvbiA8LSByYmluZGxpc3QoZXZvbHV0aW9uKQ0KY29sbmFtZXMoZXZvbHV0aW9uKSA8LSBjKCJJdGVyYXRpb24iLCAiRXhhY3RpdHVkZSIsICJGb2xkIikNCmV2b2x1dGlvbiRFeGFjdGl0dWRlIDwtIDEgLSBldm9sdXRpb24kRXhhY3RpdHVkZQ0KZXZvbHV0aW9uJEZvbGQgPC0gYXMuZmFjdG9yKGV2b2x1dGlvbiRGb2xkKQ0KDQojIFByw6lkaWN0aW9uIMOgIHBhcnRpciBkZXMgcHJvYmFiaWxpdMOpcw0KcHJlZGljdGVkTGFiZWwgPC0gZGF0YS5mcmFtZShMYWJlbCA9IGdyb3VwX3BhdGgkcGF0aF9JRCwgUHJlZGljdGlvbiA9IGFwcGx5KHByZWRpY3RlZFZhbHVlcywgMSwgZnVuY3Rpb24oeCkge3doaWNoLm1heCh4KX0pKQ0KDQp0aW1pbmcoQ3VycmVudFRpbWUsICJQcsOpcGFyYXRpb24gZGUgbCdhbmFseXNlIGR1IG1vZMOobGUgZGUgcsOpZ3Jlc3Npb24gbG9naXN0aXF1ZSIpDQoNCiMgQWZmaWNoYWdlIGRlIGwnw6l2b2x1dGlvbiBkZSBsYSBwZXJmb3JtYW5jZSBkdSBtb2TDqGxlIHNlbG9uIGxlIG5vbWJyZSBkJ2l0w6lyYXRpb24sIHNvdXMgZm9ybWUgZGUgcGxvdCBpbnRlcmFjdGlmDQpnZ3Bsb3RseShnZ3Bsb3QoZGF0YSA9IGV2b2x1dGlvbiwgYWVzX3N0cmluZyh4ID0gIkl0ZXJhdGlvbiIsIHkgPSAiRXhhY3RpdHVkZSIsIGdyb3VwID0gIkZvbGQiLCBjb2xvciA9ICJGb2xkIikpICsgZ2VvbV9saW5lKCkgKyBnZW9tX3BvaW50KCkgKyBzY2FsZV9jb2xvcl9icmV3ZXIocGFsZXR0ZSA9ICJTZXQyIikgKyB0aGVtZV9idygpICsgbGFicyh0aXRsZSA9ICJFdm9sdXRpb24gZGUgbCdleGFjdGl0dWRlIHBhciByYXBwb3J0IGF1IG5vbWJyZSBkJ2l0w6lyYXRpb25zIGQnZW50cmFpbmVtZW50IiksIHdpZHRoID0gOTYwLCBoZWlnaHQgPSA3MjApDQoNCiMgQWZmaWNoYWdlIGRlIGxhIG1hdHJpY2UgZGUgY29uZnVzaW9uIHNvdXMgZm9ybWUgZGUgcGxvdCBpbnRlcmFjdGlmDQpjb25mdXNpb25fbWF0IDwtIGV4cGFuZC5ncmlkKExhYmVsID0gMTo2LCBQcmVkaWN0aW9uID0gMTo2KQ0KY29uZnVzaW9uX21hdCA8LSBtZXJnZShjb25mdXNpb25fbWF0LCBkYXRhLnRhYmxlKHByZWRpY3RlZExhYmVsKVssIGxpc3QoRnJlcSA9IHN1bSguTikpLCBieSA9IGxpc3QoTGFiZWwsIFByZWRpY3Rpb24pXSwgYnkgPSBjKCJMYWJlbCIsICJQcmVkaWN0aW9uIiksIGFsbC54ID0gVFJVRSkNCmNvbmZ1c2lvbl9tYXRbWyJGcmVxIl1dW2lzLm5hKGNvbmZ1c2lvbl9tYXRbWyJGcmVxIl1dKV0gPC0gMA0KZ2dwbG90bHkoZ2dwbG90KCkgKyBnZW9tX3JlY3QoZGF0YSA9IGRhdGEuZnJhbWUoY2VudCA9IDE6NiksIHNpemUgPSAyLCBmaWxsID0gTkEsIGNvbG91ciA9ICJibGFjayIsIGFlcyh4bWluID0gY2VudCAtIDAuNSwgeG1heCA9IGNlbnQgKyAwLjUsIHltaW4gPSBjZW50IC0gMC41LCB5bWF4ID0gY2VudCArIDAuNSkpICsgZ2VvbV90aWxlKGRhdGEgPSBjb25mdXNpb25fbWF0LCBhZXNfc3RyaW5nKHggPSAiTGFiZWwiLCB5ID0gIlByZWRpY3Rpb24iLCBmaWxsID0gIkZyZXEiKSkgKyBnZW9tX3RleHQoZGF0YSA9IGNvbmZ1c2lvbl9tYXQsIGFlc19zdHJpbmcoeCA9ICJMYWJlbCIsIHkgPSAiUHJlZGljdGlvbiIsIGxhYmVsID0gIkZyZXEiKSkgKyBzY2FsZV94X2Rpc2NyZXRlKG5hbWUgPSAiVHJhamVjdG9pcmUgUsOpZWxsZSIpICsgc2NhbGVfeV9kaXNjcmV0ZShuYW1lID0gIlRyYWplY3RvaXJlIFByw6lkaXRlIikgKyBzY2FsZV9maWxsX2dyYWRpZW50bihjb2xvdXJzID0gcmV2KGJyZXdlci5wYWxfZXh0ZW5kZWQoMywgIlBpWUciKSkpICsgbGFicyh0aXRsZSA9ICJNYXRyaWNlIGRlIENvbmZ1c2lvbiBkZSBsYSBUcmFqZWN0b2lyZSIsIGZpbGwgPSAiRnLDqXF1ZW5jZSIpLCB3aWR0aCA9IDk2MCwgaGVpZ2h0ID0gNzIwKQ0KDQojIEFmZmljaGFnZSBkZXMgdGFibGVzIMOgIGxhIGZpbiBjYXIgbGUgZm9ybWF0dGFnZSBwb3Nzw6hkZSB1biBidWcgaW5ow6lyZW50IGxvcnNxdSdvbiBhIHBsdXNpZXVycyBkYXRhdGFibGVzIChEVCkgZGFucyBsZSBtw6ptZSBjaHVuaw0KIyBodG1sdG9vbHM6OnRhZ0xpc3QodGVtcF9kdFtbM11dLCB0ZW1wX2R0W1syXV0sIHRlbXBfZHRbWzFdXSwgdGVtcF9kdFtbNF1dKQ0KYGBgDQoNCmBgYHtyIFRlbXAxLCBlY2hvPUZBTFNFfQ0KdGVtcF9kdFtbM11dICMgQ29lZmZpY2llbnRzIGNvbnRyZSBsYSBzYWxsZSAzDQpgYGANCg0KYGBge3IgVGVtcDIsIGVjaG89RkFMU0V9DQp0ZW1wX2R0W1syXV0gIyBDb2VmZmljaWVudHMgY29udHJlIGxhIHNhbGxlIDINCmBgYA0KDQpgYGB7ciBUZW1wMywgZWNobz1GQUxTRX0NCnRlbXBfZHRbWzFdXSAjIENvZWZmaWNpZW50cyBjb250cmUgbGEgc2FsbGUgMQ0KYGBgDQoNCmBgYHtyIFRlbXA0LCBlY2hvPUZBTFNFfQ0KdGVtcF9kdFtbNF1dICMgQ29lZmZpY2llbnRzIGFncsOpZ8Opcw0KYGBgDQoNCiMgVHJvaXNpw6htZSBBbmFseXNlIFN5c3TDqW1pcXVlDQoNCk5vdHJlIGRlcm5pw6hyZSBhbmFseXNlIHN5c3TDqW1pcXVlIHBvcnRlIHN1ciBsJ3V0aWxpc2F0aW9uIGRlcyBhbmNyZXMgcXVpIHNvbnQgY2Vuc8OpZXMgw6p0cmUgY29ycmVjdGVzIDogaWwgZmF1dCBpbnZlcnNlciBsZXMgYW5jcmVzIDEgZXQgMywgZXQgMiBldCA0IGRlIGxhIHNhbGxlIDEuDQoNCiMjIENvcnJlY3Rpb24gZmluYWxlIGRlcyBhbmNyZXMNCg0KUG91ciBjb3JyaWdlciBsZXMgYW5jcmVzLCBpbCBzdWZmaXQgZGUgcsOpYWxpc2VyIGNldHRlIG9ww6lyYXRpb24gc3VyIGxhIHNhbGxlIDEgOg0KDQotIEFuY3JlIDEgPT4gQW5jcmUgMw0KLSBBbmNyZSAyID0+IEFuY3JlIDQNCi0gQW5jcmUgMyA9PiBBbmNyZSAxDQotIEFuY3JlIDQgPT4gQW5jcmUgMg0KDQpgYGB7ciBDb3JyZWN0aW9uRmluYWxlfQ0KIyBDb21wdGV1ciBkZSB0ZW1wcw0KQ3VycmVudFRpbWUgPC0gdGltZXIoKSAjIENodW5rIENvcnJlY3Rpb24gZGVzIGFuY3Jlcw0KDQojIFJlY29waWUgZGVzIGZlYXR1cmVzIGRhbnMgbGUgc2VucyBjb3JyZWN0IChhbmNyZSAxPD0+MywgYW5jcmUgMjw9PjQpDQpmb3IgKGkgaW4gd2hpY2goZ3JvdXBfcm9vbSRkYXRhc2V0X0lEID09IDEpKSB7DQogIGRhdGFfcHJlW1tpXV1bWzFdXSA8LSBkYXRhX3ByZV9vbGRbW2ldXVtbM11dDQogIGRhdGFfcHJlW1tpXV1bWzNdXSA8LSBkYXRhX3ByZV9vbGRbW2ldXVtbMV1dDQogIGRhdGFfcHJlW1tpXV1bWzJdXSA8LSBkYXRhX3ByZV9vbGRbW2ldXVtbNF1dDQogIGRhdGFfcHJlW1tpXV1bWzRdXSA8LSBkYXRhX3ByZV9vbGRbW2ldXVtbMl1dDQp9DQoNCiMgRW5yZWdpc3RyZW1lbnQgZGVzIGZyYW1lcyBjb3JyaWfDqWVzIGV0IGRlcyBhbmNpZW5uZXMgZnJhbWVzLCBwb3VyIGF2b2lyIHVuIHNldCBzb2xpZGUsIGVuIHJlc3BlY3RhbnQgbGEgbm9tZW5jbGF0dXJlIGluaXRpYWxlIE1vdmVtZW50QUFMX1JTU194eHguY3N2Lg0KZm9yIChpIGluIDE6MzE0KSB7DQogIGZ3cml0ZShkYXRhX3ByZVtbaV1dLCBwYXN0ZTAoImRhdGFzZXRfY29ycmVjdGVkL01vdmVtZW50QUFMX1JTU18iLCBpLCAiLmNzdiIpKQ0KfQ0KDQojIFRlbXBzIG7DqWNlc3NhaXJlDQp0aW1pbmcoQ3VycmVudFRpbWUsICJDb3JyZWN0aW9uIGRlcyBhbmNyZXMiKQ0KYGBgDQoNCiMjIENyw6lhdGlvbiBkZXMgZmVhdHVyZXMgY29ycmlnw6llcw0KDQpVbmUgZm9pcyBxdWUgbGVzIGFuY3JlcyBjb3JyZXNwb25kYW50IGF1eCBib25uZXMgYW5jcmVzIGRhbnMgbGEgc2FsbGUgMSwgb24gcGV1dCBjcsOpZXIgbGVzIGZlYXR1cmVzIGNvcnJpZ8OpZXMuDQoNCmBgYHtyIENyZWF0aW9uRmVhdHVyZXMzfQ0KIyBDb21wdGV1ciBkZSB0ZW1wcw0KQ3VycmVudFRpbWUgPC0gdGltZXIoKSAjIENodW5rIENyw6lhdGlvbiBkZXMgZmVhdHVyZXMgZmluYWxlcw0KDQojIFByw6ktaW5pdGlhbGlzYXRpb24gZGUgbGEgZnJhbWUNCm1pbmlfbG0gPC0gZGF0YS5mcmFtZShtYXRyaXgobnJvdyA9IDMxNCwgbmNvbCA9IDM2KSkNCg0KIyBCb3VjbGUgcGFyIHPDqXJpZSB0ZW1wb3JlbGxlDQpmb3IgKGkgaW4gMTozMTQpIHsNCiAgDQogICMgQm91Y2xlIHBhciBhbmNyZQ0KICBmb3IgKGogaW4gMTo0KSB7DQogICAgDQogICAgIyBFbnRyYWluZW1lbnQgZCd1biBtb2TDqGxlIGxpbsOpYWlyZSB1dGlsaXNhbnQgbGVzIGF1dHJlcyBhbmNyZXMsIGF2ZWMgbCdpbnRlcmNlcHRyaWNlDQogICAgdGVtcF9tb2RlbCA8LSBmYXN0TG1QdXJlKFggPSBjYmluZChhcy5tYXRyaXgoZGF0YV9wcmVbW2ldXVssICgxOjQpWy1qXSwgd2l0aCA9IEZBTFNFXSksIHJlcCgxLCBucm93KGRhdGFfcHJlW1tpXV0pKSksIHkgPSBkYXRhX3ByZVtbaV1dW1tqXV0pDQogICAgDQogICAgIyBFbnJlZ2lzdHJlbWVudCBkZXMgY29lZmZpY2llbnRzIGV0IGRlcyByw6lzaWR1cw0KICAgIG1pbmlfbG1baSwgKGogKiA4IC0gNyk6KGogKiA4KV0gPC0gYyh0ZW1wX21vZGVsJGNvZWZmaWNpZW50cywgdGVtcF9tb2RlbCRzdGRlcnIpDQogICAgDQogIH0NCiAgDQogICMgQWpvdXQgZHUgZGVybmllciDDqWzDqW1lbnQgZGUgbGEgc8OpcmllIHRlbXBvcmVsbGUgKDQgYW5jcmVzKQ0KICBtaW5pX2xtW2ksIDMzOjM2XSA8LSBkYXRhX3ByZVtbaV1dW25yb3coZGF0YV9wcmVbW2ldXSksIF0NCiAgDQp9DQoNCiMgRW5yZWdpc3RyZW1lbnQgZGVzIGRvbm7DqWVzIGF1IGZvcm1hdCBDU1YNCmZ3cml0ZShjYmluZChtaW5pX2xtLCBHcm91cCA9IGdyb3VwX3Jvb21bWyJkYXRhc2V0X0lEIl1dLCBMYWJlbCA9IGdyb3VwX3BhdGhbWyJwYXRoX0lEIl1dKSwgImZlYXR1cmVzL2ZlYXR1cmVzMy5jc3YiKQ0KDQojIFRlbXBzIG7DqWNlc3NhaXJlDQp0aW1pbmcoQ3VycmVudFRpbWUsICJDcsOpYXRpb24gZGVzIGZlYXR1cmVzIGZpbmFsZXMiKQ0KYGBgDQoNCiMjIEVucmVnaXN0cmVtZW50IGRlcyBmZWF0dXJlcw0KDQpVbmUgZm9pcyBjb3JyaWfDqWVzLCBvbiBlbnJlZ2lzdHJlIGxlcyBmZWF0dXJlcyBjb21tZSBvbiBhIGZhaXQgYXUgZMOpYnV0Lg0KDQpgYGB7ciBHZW5lcmF0aW9uRG9ubmVlczN9DQojIENvbXB0ZXVyIGRlIHRlbXBzDQpDdXJyZW50VGltZSA8LSB0aW1lcigpICMgUHLDqXBhcmF0aW9uIGRlIGwnw6l2YWx1YXRpb24gZHUgbW9kw6hsZSBmaW5hbA0KDQojIE/DuSBzYXV2ZWdhcmRlciBsZXMgZmljaGllcnMgPw0KZmlsZV90YWcgPC0gIjNfZGF0YS8iDQoNCiMgSW5pdGlhbGlzYXRpb24gZGUgbGEgdmFyaWFibGUgcXVpIGFjY3VlaWxsZXJhIGxhIHByw6ljaXNpb24NCmFjY3VyYWN5IDwtIGRhdGEuZnJhbWUobWF0cml4KG5yb3cgPSAxNiwgbmNvbCA9IDEzKSkNCmNvbG5hbWVzKGFjY3VyYWN5KSA8LSBjKCJGb2xkIiwgInhnYl9MaW5lYXJNb2RlbCIsICJ4Z2JfRGVjaXNpb25UcmVlIiwgInhnYl9SYW5kb21Gb3Jlc3QiLCAieGdiX0dyYWRpZW50Qm9vc3RpbmciLCAiaDJvX0xpbmVhck1vZGVsIiwgImgyb19EZWNpc2lvblRyZWUiLCAiaDJvX1JhbmRvbUZvcmVzdCIsICJoMm9fR3JhZGllbnRCb29zdGluZyIsICJoMm9fTk5fMzJ4Nl9SZUxVIiwgImgyb19OTl8zMng2X1NvZnQiLCAiaDJvX05OXzE2eDE2eDZfUmVMVSIsICJoMm9fTk5fMTZ4MTZ4Nl9Tb2Z0IikNCmFjY3VyYWN5WywgMV0gPC0gYygiRm9sZF8xdjIiLCAiRm9sZF8xdjMiLCAiRm9sZF8ydjEiLCAiRm9sZF8ydjMiLCAiRm9sZF8zdjEiLCAiRm9sZF8zdjIiLCAiRm9sZF8xdjIzIiwgIkZvbGRfMnYxMyIsICJGb2xkXzN2MTIiLCAiRm9sZF8xMnYzIiwgIkZvbGRfMTN2MiIsICJGb2xkXzIzdjEiLCAiTW95ZW5uZV8xYzEiLCAiTW95ZW5uZV8xYzIiLCAiTW95ZW5uZV8yYzEiLCAiTW95ZW5uZSIpDQoNCiMgSW5pdGlhbGlzYXRpb24gZGVzIGZvbGRzIHBvdXIgbGEgY3Jvc3MtdmFsaWRhdGlvbg0KZm9sZHNfdHJhaW4gPC0gbGlzdCgpDQpmb2xkc190ZXN0IDwtIGxpc3QoKQ0KdHJhaW5pbmdfZGF0YSA8LSBsaXN0KCkNCnRlc3RpbmdfZGF0YSA8LSBsaXN0KCkNCnRyYWluaW5nX3hnYiA8LSBsaXN0KCkNCnRlc3RpbmdfeGdiIDwtIGxpc3QoKQ0KdHJhaW5pbmdfaDJvIDwtIGxpc3QoKQ0KdGVzdGluZ19oMm8gPC0gbGlzdCgpDQpjb21iaW5hdGlvbnNfdHJhaW4gPC0gYyhsaXN0KDEsIDEsIDIsIDIsIDMsIDMpLCBjb21ibigzLCAxLCBzaW1wbGlmeSA9IEZBTFNFKSwgY29tYm4oMywgMiwgc2ltcGxpZnkgPSBGQUxTRSkpDQpjb21iaW5hdGlvbnNfdGVzdCA8LSBjKGxpc3QoMiwgMywgMSwgMywgMSwgMiksIHJldihjb21ibigzLCAyLCBzaW1wbGlmeSA9IEZBTFNFKSksIHJldihjb21ibigzLCAxLCBzaW1wbGlmeSA9IEZBTFNFKSkpDQp0ZW1wX2ZhY3RvcnMgPC0gYXMuZmFjdG9yKGdyb3VwX3BhdGgkcGF0aF9JRCkNCg0KIyBDcsOpYXRpb24gZGVzIGRvbm7DqWVzIGQnZW50cmFpbmVtZW50IGV0IGRlIHZhbGlkYXRpb24NCmZvciAoaSBpbiAxOjEyKSB7DQogIA0KICAjIENyw6lhdGlvbiBkZXMgZm9sZHMgZCdlbnRyYWluZW1lbnQgZXQgZGUgdmFsaWRhdGlvbg0KICBmb2xkc190cmFpbltbaV1dIDwtIHdoaWNoKGdyb3VwX3Jvb21bWyJkYXRhc2V0X0lEIl1dICVpbiUgY29tYmluYXRpb25zX3RyYWluW1tpXV0pDQogIGZvbGRzX3Rlc3RbW2ldXSA8LSB3aGljaChncm91cF9yb29tW1siZGF0YXNldF9JRCJdXSAlaW4lIGNvbWJpbmF0aW9uc190ZXN0W1tpXV0pDQogIA0KICAjIFJlY2hlcmNoZSBldCBzdXBwcmVzc2lvbiBkdSBsYWJlbCAzIGxvcnNxdWUgbGEgc2FsbGUgMSBlc3QgaXNvbMOpZSAoc29pdCBlbiB0cmFpbiBvbiBlbmzDqHZlIGVuIHRlc3QsIHNvaXQgZW4gdGVzdCBvbiBlbmzDqHZlIGVuIHRyYWluKQ0KICBpZiAoKGxlbmd0aChjb21iaW5hdGlvbnNfdHJhaW5bW2ldXSkgPT0gMSkgJiAoY29tYmluYXRpb25zX3RyYWluW1tpXV1bMV0gPT0gMSkpIHsNCiAgICBmb2xkc190ZXN0W1tpXV0gPC0gZm9sZHNfdGVzdFtbaV1dW2dyb3VwX3BhdGgkcGF0aF9JRFtmb2xkc190ZXN0W1tpXV1dICE9IDNdDQogIH0NCiAgaWYgKChsZW5ndGgoY29tYmluYXRpb25zX3Rlc3RbW2ldXSkgPT0gMSkgJiAoY29tYmluYXRpb25zX3Rlc3RbW2ldXVsxXSA9PSAxKSkgew0KICAgIGZvbGRzX3RyYWluW1tpXV0gPC0gZm9sZHNfdHJhaW5bW2ldXVtncm91cF9wYXRoJHBhdGhfSURbZm9sZHNfdHJhaW5bW2ldXV0gIT0gM10NCiAgfQ0KICANCiAgIyBDcsOpYXRpb24gZGVzIGRvbm7DqWVzIGQnZW50cmFpbmVtZW50IGV0IGRlIHZhbGlkYXRpb24NCiAgdHJhaW5pbmdfZGF0YVtbaV1dIDwtIG1pbmlfbG1bZm9sZHNfdHJhaW5bW2ldXSwgXQ0KICB0ZXN0aW5nX2RhdGFbW2ldXSA8LSBtaW5pX2xtW2ZvbGRzX3Rlc3RbW2ldXSwgXQ0KICANCiAgIyBFbnJlZ2lzdHJlbWVudCBkZXMgZG9ubsOpZXMgQ1NWDQogIGZ3cml0ZSh0cmFpbmluZ19kYXRhW1tpXV0sIHBhc3RlMChmaWxlX3RhZywgInRyYWluTkxfIiwgc3ByaW50ZigiJTAyZCIsIGkpLCAiLmNzdiIpKQ0KICBmd3JpdGUodGVzdGluZ19kYXRhW1tpXV0sIHBhc3RlMChmaWxlX3RhZywgInRlc3ROTF8iLCBzcHJpbnRmKCIlMDJkIiwgaSksICIuY3N2IikpDQogIA0KICAjIFRyYW5zZm9ybWF0aW9uIGRlcyBkb25uw6llcyBhdSBmb3JtYXQgYXBwcm9wcmnDqSBwb3VyIHhnYm9vc3QNCiAgdHJhaW5pbmdfeGdiW1tpXV0gPC0geGdiLkRNYXRyaXgoZGF0YSA9IGFzLm1hdHJpeCh0cmFpbmluZ19kYXRhW1tpXV0pLCBsYWJlbCA9IGdyb3VwX3BhdGgkcGF0aF9JRFtmb2xkc190cmFpbltbaV1dXSAtIDEpDQogIHRlc3RpbmdfeGdiW1tpXV0gPC0geGdiLkRNYXRyaXgoZGF0YSA9IGFzLm1hdHJpeCh0ZXN0aW5nX2RhdGFbW2ldXSksIGxhYmVsID0gZ3JvdXBfcGF0aCRwYXRoX0lEW2ZvbGRzX3Rlc3RbW2ldXV0gLSAxKQ0KICANCiAgIyBEdW1waW5nIGRlcyBkYXRhc2V0cyBiaW5haXJlcyB4Z2Jvb3N0DQogIHhnYi5ETWF0cml4LnNhdmUodHJhaW5pbmdfeGdiW1tpXV0sIHBhc3RlMChmaWxlX3RhZywgInRyYWluTF8iLCBzcHJpbnRmKCIlMDJkIiwgaSksICIuZGF0YSIpKQ0KICB4Z2IuRE1hdHJpeC5zYXZlKHRlc3RpbmdfeGdiW1tpXV0sIHBhc3RlMChmaWxlX3RhZywgInRlc3RMXyIsIHNwcmludGYoIiUwMmQiLCBpKSwgIi5kYXRhIikpDQogIA0KICAjIFRyYW5zZm9ybWF0aW9uIGRlcyBkb25uw6llcyBhdSBmb3JtYXQgYXBwcm9wcmnDqSBwb3VyIEgyTw0KICB0cmFpbmluZ19oMm9bW2ldXSA8LSBhcy5oMm8oY2JpbmQoTGFiZWwgPSB0ZW1wX2ZhY3RvcnNbZm9sZHNfdHJhaW5bW2ldXV0sIHRyYWluaW5nX2RhdGFbW2ldXSkpDQogIHRlc3RpbmdfaDJvW1tpXV0gPC0gYXMuaDJvKGNiaW5kKExhYmVsID0gdGVtcF9mYWN0b3JzW2ZvbGRzX3Rlc3RbW2ldXV0sIHRlc3RpbmdfZGF0YVtbaV1dKSkNCiAgDQogICMgRW5yZWdpc3RyZW1lbnQgZGVzIGZyYW1lcyBIMk8gKENTViArIExhYmVsKQ0KICBoMm8uZXhwb3J0RmlsZSh0cmFpbmluZ19oMm9bW2ldXSwgcGFzdGUwKGZpbGVfdGFnLCAidHJhaW5MXyIsIHNwcmludGYoIiUwMmQiLCBpKSwgIi5jc3YiKSwgZm9yY2UgPSBUUlVFKQ0KICBoMm8uZXhwb3J0RmlsZSh0ZXN0aW5nX2gyb1tbaV1dLCBwYXN0ZTAoZmlsZV90YWcsICJ0ZXN0TF8iLCBzcHJpbnRmKCIlMDJkIiwgaSksICIuY3N2IiksIGZvcmNlID0gVFJVRSkNCiAgDQp9DQoNCiMgVGVtcHMgbsOpY2Vzc2FpcmUNCnRpbWluZyhDdXJyZW50VGltZSwgIlByw6lwYXJhdGlvbiBkZSBsJ8OpdmFsdWF0aW9uIGR1IG1vZMOobGUgZmluYWwiKQ0KYGBgDQoNCiMjIEVudHJhaW5lbWVudCBkZXMgZG91emUgbW9kw6hsZXMNCg0KT24gcGV1dCBtYWludGVuYW50IHRlc3RlciBsZXMgZG91emUgbW9kw6hsZXMgZGUgbWFuacOocmUgZmlhYmxlLg0KDQpgYGB7ciBFbnRyYWluZW1lbnQzLCBjYWNoZT1UUlVFfQ0KIyBDb21wdGV1ciBkZSB0ZW1wcw0KQ3VycmVudFRpbWUgPC0gdGltZXIoKSAjIENodW5rIENyw6lhdGlvbiBldCDDqXZhbHVhdGlvbiBkZXMgZG91emUgbW9kw6hsZXMgZGUgYmVuY2htYXJrIGZpbmFsDQoNCiMgT8O5IHNhdXZlZ2FyZGVyIGxlcyBmaWNoaWVycyA/DQpmaWxlX3RhZyA8LSAiM19tb2RlbHMvIg0KZmlsZV9oMm8gPC0gIjNfbW9kZWxzIg0KDQojIEJvdWNsZSBkJ8OpdmFsdWF0aW9uDQpmb3IgKGkgaW4gMToxMikgew0KICANCiAgIyBFbnRyYWluZW1lbnQgZHUgbW9kw6hsZSBkZSByw6lncmVzc2lvbiBsb2dpc3RpcXVlICh4Z2Jvb3N0KQ0KICB0ZW1wX21vZGVsIDwtIHhnYl9keW5hbWljX3RyYWluKHRyYWluID0gdHJhaW5pbmdfeGdiW1tpXV0sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGVzdCA9IHRlc3RpbmdfeGdiW1tpXV0sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYm9vc3RlciA9ICJnYmxpbmVhciIsICMgTGluw6lhaXJlDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbnJvdW5kcyA9IDEwMDAwMDAsICMgQXJyw6p0w6kgYXUgbWVpbGxldXIgcsOpc3VsdGF0DQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbnVtX3BhcmFsbGVsX3RyZWVzID0gMSkNCiAgeGdiLmR1bXAobW9kZWwgPSB0ZW1wX21vZGVsLCAjIE1vZMOobGUgw6AgZW5yZWdpc3RyZXINCiAgICAgICAgICAgZm5hbWUgPSBwYXN0ZTAoZmlsZV90YWcsICJ4Z2JfZ2xtXyIsIHNwcmludGYoIiUwMmQiLCBpKSwgIi5qc29uIiksICMgT8O5IGVucmVnaXN0cmVyIGxlIG1vZMOobGUgPw0KICAgICAgICAgICB3aXRoX3N0YXRzID0gVFJVRSwgIyBFbnJlZ2lzdHJlbWVudCBkZXMgc3RhdGlzdGlxdWVzIHNpIG1vZMOobGUgZ2J0cmVlDQogICAgICAgICAgIGR1bXBfZm9ybWF0ID0gImpzb24iKSAjIER1bXAgYXUgZm9ybWF0IGpzb24sIHLDqS11dGlsaXNhYmxlDQogIGFjY3VyYWN5W2ksIDJdIDwtIDEgLSB0ZW1wX21vZGVsJGV2YWx1YXRpb25fbG9nW1syXV1bdGVtcF9tb2RlbCRiZXN0X2l0ZXJhdGlvbl0gIyBSw6ljdXDDqXJhdGlvbiBkdSBtZWlsbGV1ciByw6lzdWx0YXQNCiAgDQogICMgRW50cmFpbmVtZW50IGR1IG1vZMOobGUgZCdhcmJyZSBkZSBkw6ljaXNpb24gKHhnYm9vc3QpDQogIHRlbXBfbW9kZWwgPC0geGdiX2R5bmFtaWNfdHJhaW4odHJhaW4gPSB0cmFpbmluZ194Z2JbW2ldXSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0ZXN0ID0gdGVzdGluZ194Z2JbW2ldXSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBib29zdGVyID0gImdidHJlZSIsICMgTm9uLWxpbsOpYWlyZQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5yb3VuZHMgPSAxLCAjIFVuIHNldWwgYXJicmUNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBudW1fcGFyYWxsZWxfdHJlZXMgPSAxKQ0KICB4Z2IuZHVtcChtb2RlbCA9IHRlbXBfbW9kZWwsICMgTW9kw6hsZSDDoCBlbnJlZ2lzdHJlcg0KICAgICAgICAgICBmbmFtZSA9IHBhc3RlMChmaWxlX3RhZywgInhnYl9kdF8iLCBzcHJpbnRmKCIlMDJkIiwgaSksICIuanNvbiIpLCAjIE/DuSBlbnJlZ2lzdHJlciBsZSBtb2TDqGxlID8NCiAgICAgICAgICAgd2l0aF9zdGF0cyA9IFRSVUUsICMgRW5yZWdpc3RyZW1lbnQgZGVzIHN0YXRpc3RpcXVlcyBzaSBtb2TDqGxlIGdidHJlZQ0KICAgICAgICAgICBkdW1wX2Zvcm1hdCA9ICJqc29uIikgIyBEdW1wIGF1IGZvcm1hdCBqc29uLCByw6ktdXRpbGlzYWJsZQ0KICBhY2N1cmFjeVtpLCAzXSA8LSAxIC0gdGVtcF9tb2RlbCRldmFsdWF0aW9uX2xvZ1tbMl1dWzFdICMgUsOpY3Vww6lyYXRpb24gZHUgbWVpbGxldXIgcsOpc3VsdGF0DQogIA0KICAjIEVudHJhaW5lbWVudCBkdSBtb2TDqGxlIGRlIFJhbmRvbSBGb3Jlc3QgKHhnYm9vc3QpDQogIHRlbXBfbW9kZWwgPC0geGdiX2R5bmFtaWNfdHJhaW4odHJhaW4gPSB0cmFpbmluZ194Z2JbW2ldXSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0ZXN0ID0gdGVzdGluZ194Z2JbW2ldXSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBib29zdGVyID0gImdidHJlZSIsICMgTm9uLWxpbsOpYWlyZQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5yb3VuZHMgPSAxLCAjIFVuZSBzZXVsZSBpdMOpcmF0aW9uDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbnVtX3BhcmFsbGVsX3RyZWVzID0gMjAwKSAjIERlIDIwMCBhcmJyZXMNCiAgeGdiLmR1bXAobW9kZWwgPSB0ZW1wX21vZGVsLCAjIE1vZMOobGUgw6AgZW5yZWdpc3RyZXINCiAgICAgICAgICAgZm5hbWUgPSBwYXN0ZTAoZmlsZV90YWcsICJ4Z2JfcmZfIiwgc3ByaW50ZigiJTAyZCIsIGkpLCAiLmpzb24iKSwgIyBPw7kgZW5yZWdpc3RyZXIgbGUgbW9kw6hsZSA/DQogICAgICAgICAgIHdpdGhfc3RhdHMgPSBUUlVFLCAjIEVucmVnaXN0cmVtZW50IGRlcyBzdGF0aXN0aXF1ZXMgc2kgbW9kw6hsZSBnYnRyZWUNCiAgICAgICAgICAgZHVtcF9mb3JtYXQgPSAianNvbiIpICMgRHVtcCBhdSBmb3JtYXQganNvbiwgcsOpLXV0aWxpc2FibGUNCiAgYWNjdXJhY3lbaSwgNF0gPC0gMSAtIHRlbXBfbW9kZWwkZXZhbHVhdGlvbl9sb2dbWzJdXSAjIFLDqWN1cMOpcmF0aW9uIGR1IG1laWxsZXVyIHLDqXN1bHRhdA0KICANCiAgIyBFbnRyYWluZW1lbnQgZHUgbW9kw6hsZSBkJ2FyYnJlIGRlIGTDqWNpc2lvbiBib29zdMOpIGF2ZWMgcHJvdGVjdGlvbiBjb250cmUgbCdvdmVyZml0dGluZyAoeGdib29zdCkNCiAgdGVtcF9tb2RlbCA8LSB4Z2JfZHluYW1pY190cmFpbih0cmFpbiA9IHRyYWluaW5nX3hnYltbaV1dLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRlc3QgPSB0ZXN0aW5nX3hnYltbaV1dLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJvb3N0ZXIgPSAiZ2J0cmVlIiwgIyBOb24tbGluw6lhaXJlDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbnJvdW5kcyA9IDEwMDAwMDAsICMgQXJyw6p0w6kgYXUgbWVpbGxldXIgcsOpc3VsdGF0DQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbnVtX3BhcmFsbGVsX3RyZWVzID0gMSkNCiAgeGdiLmR1bXAobW9kZWwgPSB0ZW1wX21vZGVsLCAjIE1vZMOobGUgw6AgZW5yZWdpc3RyZXINCiAgICAgICAgICAgZm5hbWUgPSBwYXN0ZTAoZmlsZV90YWcsICJ4Z2JfZ2J0XyIsIHNwcmludGYoIiUwMmQiLCBpKSwgIi5qc29uIiksICMgT8O5IGVucmVnaXN0cmVyIGxlIG1vZMOobGUgPw0KICAgICAgICAgICB3aXRoX3N0YXRzID0gVFJVRSwgIyBFbnJlZ2lzdHJlbWVudCBkZXMgc3RhdGlzdGlxdWVzIHNpIG1vZMOobGUgZ2J0cmVlDQogICAgICAgICAgIGR1bXBfZm9ybWF0ID0gImpzb24iKSAjIER1bXAgYXUgZm9ybWF0IGpzb24sIHLDqS11dGlsaXNhYmxlDQogIGFjY3VyYWN5W2ksIDVdIDwtIDEgLSB0ZW1wX21vZGVsJGV2YWx1YXRpb25fbG9nW1syXV1bdGVtcF9tb2RlbCRiZXN0X2l0ZXJhdGlvbl0gIyBSw6ljdXDDqXJhdGlvbiBkdSBtZWlsbGV1ciByw6lzdWx0YXQNCiAgDQogICMgRW50cmFpbmVtZW50IGR1IG1vZMOobGUgZGUgcsOpZ3Jlc3Npb24gbG9naXN0aXF1ZSAoaDJvKQ0KICB0ZW1wX21vZGVsIDwtIGgyby5nbG0oeSA9IDEsDQogICAgICAgICAgICAgICAgICAgICAgICB0cmFpbmluZ19mcmFtZSA9IHRyYWluaW5nX2gyb1tbaV1dLA0KICAgICAgICAgICAgICAgICAgICAgICAgdmFsaWRhdGlvbl9mcmFtZSA9IHRlc3RpbmdfaDJvW1tpXV0sDQogICAgICAgICAgICAgICAgICAgICAgICBtb2RlbF9pZCA9IHBhc3RlMCgiaDJvX2dsbV8iLCBzcHJpbnRmKCIlMDJkIiwgaSkpLCAjIE5vbSBkdSBtb2TDqGxlDQogICAgICAgICAgICAgICAgICAgICAgICBtYXhfaXRlcmF0aW9ucyA9IDEwMCwgIyAxMDAgaXTDqXJhdGlvbnMgZCdvcHRpbWlzYXRpb24NCiAgICAgICAgICAgICAgICAgICAgICAgIHNvbHZlciA9ICJJUkxTTSIsICMgU29sdmV1ciBwYXIgZMOpZmF1dA0KICAgICAgICAgICAgICAgICAgICAgICAgc3RhbmRhcmRpemUgPSBGQUxTRSwgIyBQYXMgZGUgc3RhbmRhcmRpc2F0aW9uIHB1aXNxdWUgWy0xLCAxXQ0KICAgICAgICAgICAgICAgICAgICAgICAgZmFtaWx5ID0gIm11bHRpbm9taWFsIiwgIyBDbGFzc2lmaWNhdGlvbiBtdWx0aS1jbGFzc2UNCiAgICAgICAgICAgICAgICAgICAgICAgIHNlZWQgPSAwLCAjIFJlcHJvZHVjdGlvbiBkZXMgcsOpc3VsdGF0cw0KICAgICAgICAgICAgICAgICAgICAgICAgaW50ZXJjZXB0ID0gVFJVRSkNCiAgaDJvLmRvd25sb2FkX3Bvam8odGVtcF9tb2RlbCwgIyBNb2TDqGxlIMOgIGVucmVnaXN0cmVyDQogICAgICAgICAgICAgICAgICAgIHBhdGggPSBmaWxlX2gybywgIyBPw7kgZW5yZWdpc3RyZXIgbGUgbW9kw6hsZSA/DQogICAgICAgICAgICAgICAgICAgIGdldF9qYXIgPSBGQUxTRSkgIyBQYXMgZGUgZmljaGllciAuamFyDQogIGFjY3VyYWN5W2ksIDZdIDwtIHRlbXBfbW9kZWxAbW9kZWwkdmFsaWRhdGlvbl9tZXRyaWNzQG1ldHJpY3MkaGl0X3JhdGlvX3RhYmxlWzEsIDJdDQogIA0KICAjIEVudHJhaW5lbWVudCBkdSBtb2TDqGxlIGQnYXJicmUgZGUgZMOpY2lzaW9uIChoMm8pDQogIHRlbXBfbW9kZWwgPC0gaDJvLnJhbmRvbUZvcmVzdCh5ID0gMSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRyYWluaW5nX2ZyYW1lID0gdHJhaW5pbmdfaDJvW1tpXV0sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB2YWxpZGF0aW9uX2ZyYW1lID0gdGVzdGluZ19oMm9bW2ldXSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1vZGVsX2lkID0gcGFzdGUwKCJoMm9fZHRfIiwgc3ByaW50ZigiJTAyZCIsIGkpKSwgIyBOb20gZHUgbW9kw6hsZQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2FtcGxlX3JhdGUgPSAxLCAjIFRvdXRlcyBsZXMgb2JzZXJ2YXRpb25zIHNlcm9udCBwcmlzZXMgZW4gY29tcHRlIHBvdXIgbGUgc2V1bCBhcmJyZSBkZSBkw6ljaXNpb24NCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG10cmllcyA9IDM2LCAjIFRvdXRlcyBsZXMgZmVhdHVyZXMgc2Vyb250IHByaXNlcyBlbiBjb21wdGUgcG91ciBsZSBzZXVsIGFyYnJlIGRlIGTDqWNpc2lvbg0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbnRyZWVzID0gMSwgIyBVbiBzZXVsIGFyYnJlDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZWVkID0gMCkgIyBSZXByb2R1Y3Rpb24gZGVzIHLDqXN1bHRhdHMNCiAgaDJvLmRvd25sb2FkX3Bvam8odGVtcF9tb2RlbCwgIyBNb2TDqGxlIMOgIGVucmVnaXN0cmVyDQogICAgICAgICAgICAgICAgICAgIHBhdGggPSBmaWxlX2gybywgIyBPw7kgZW5yZWdpc3RyZXIgbGUgbW9kw6hsZSA/DQogICAgICAgICAgICAgICAgICAgIGdldF9qYXIgPSBGQUxTRSkgIyBQYXMgZGUgZmljaGllciAuamFyDQogIGFjY3VyYWN5W2ksIDddIDwtIDEgLSBtaW4odGVtcF9tb2RlbEBtb2RlbCRzY29yaW5nX2hpc3RvcnkkdmFsaWRhdGlvbl9jbGFzc2lmaWNhdGlvbl9lcnJvciwgbmEucm0gPSBUUlVFKQ0KICANCiAgIyBFbnRyYWluZW1lbnQgZHUgbW9kw6hsZSBkZSBSYW5kb20gRm9yZXN0IChoMm8pDQogIHRlbXBfbW9kZWwgPC0gaDJvLnJhbmRvbUZvcmVzdCh5ID0gMSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRyYWluaW5nX2ZyYW1lID0gdHJhaW5pbmdfaDJvW1tpXV0sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB2YWxpZGF0aW9uX2ZyYW1lID0gdGVzdGluZ19oMm9bW2ldXSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1vZGVsX2lkID0gcGFzdGUwKCJoMm9fcmZfIiwgc3ByaW50ZigiJTAyZCIsIGkpKSwgIyBOb20gZHUgbW9kw6hsZQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2FtcGxlX3JhdGUgPSAwLjYzMiwgIyBCb290c3RyYXBwaW5nIC42MzIgcG91ciBjaGFxdWUgYXJicmUgZGUgZMOpY2lzaW9uDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtdHJpZXMgPSAtMSwgIyBzcXJ0KDM2KSBmZWF0dXJlcyBzZXJvbnQgcHJpc2VzIGVuIGNvbXB0ZSBwb3VyIGNoYXF1ZSBhcmJyZSBkZSBkw6ljaXNpb24NCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG50cmVlcyA9IDIwMCwgIyAyMDAgYXJicmVzDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZWVkID0gMCkgIyBSZXByb2R1Y3Rpb24gZGVzIHLDqXN1bHRhdHMNCiAgaDJvLmRvd25sb2FkX3Bvam8odGVtcF9tb2RlbCwgIyBNb2TDqGxlIMOgIGVucmVnaXN0cmVyDQogICAgICAgICAgICAgICAgICAgIHBhdGggPSBmaWxlX2gybywgIyBPw7kgZW5yZWdpc3RyZXIgbGUgbW9kw6hsZSA/DQogICAgICAgICAgICAgICAgICAgIGdldF9qYXIgPSBGQUxTRSkgIyBQYXMgZGUgZmljaGllciAuamFyDQogIGFjY3VyYWN5W2ksIDhdIDwtIDEgLSBtaW4odGVtcF9tb2RlbEBtb2RlbCRzY29yaW5nX2hpc3RvcnkkdmFsaWRhdGlvbl9jbGFzc2lmaWNhdGlvbl9lcnJvciwgbmEucm0gPSBUUlVFKQ0KICANCiAgIyBFbnRyYWluZW1lbnQgZHUgbW9kw6hsZSBkJ2FyYnJlIGRlIGTDqWNpc2lvbiBib29zdMOpIGF2ZWMgcHJvdGVjdGlvbiBjb250cmUgbCdvdmVyZml0dGluZyAoaDJvKQ0KICB0ZW1wX21vZGVsIDwtIGgyby5nYm0oeSA9IDEsDQogICAgICAgICAgICAgICAgICAgICAgdHJhaW5pbmdfZnJhbWUgPSB0cmFpbmluZ19oMm9bW2ldXSwNCiAgICAgICAgICAgICAgICAgICAgICB2YWxpZGF0aW9uX2ZyYW1lID0gdGVzdGluZ19oMm9bW2ldXSwNCiAgICAgICAgICAgICAgICAgICAgICBtb2RlbF9pZCA9IHBhc3RlMCgiaDJvX2didF8iLCBzcHJpbnRmKCIlMDJkIiwgaSkpLCAjIE5vbSBkdSBtb2TDqGxlDQogICAgICAgICAgICAgICAgICAgICAgZGlzdHJpYnV0aW9uID0gIm11bHRpbm9taWFsIiwgIyBDbGFzc2lmaWNhdGlvbiBtdWx0aS1jbGFzc2UNCiAgICAgICAgICAgICAgICAgICAgICBzYW1wbGVfcmF0ZSA9IDEsICMgUGFzIGRlIHByb2Nlc3N1cyBzdG9jaGFzdGlxdWUNCiAgICAgICAgICAgICAgICAgICAgICBudHJlZXMgPSAxMDAsICMgMTAwIGl0w6lyYXRpb25zIGRlIGJvb3N0aW5nIGF1IG1heGltdW0NCiAgICAgICAgICAgICAgICAgICAgICBzY29yZV9lYWNoX2l0ZXJhdGlvbiA9IFRSVUUsICMgTm90ZXIgbGEgdmFsZXVyIGRlIGNoYXF1ZSBpdMOpcmF0aW9uDQogICAgICAgICAgICAgICAgICAgICAgc3RvcHBpbmdfcm91bmRzID0gMTAsICMgQXJyw6p0IGFwcsOocyAxMCBpdMOpcmF0aW9ucyBzYW5zIGFtw6lsaW9yYXRvbiBkZSBsYSBtw6l0cmlxdWUNCiAgICAgICAgICAgICAgICAgICAgICBzdG9wcGluZ19tZXRyaWMgPSAibWlzY2xhc3NpZmljYXRpb24iLCAjIFN1cnZlaWxsZXIgbCdpbmV4YWN0aXR1ZGUgZGUgbGEgY2xhc3NpZmljYXRpb24gcG91ciBsJ2FycsOqdA0KICAgICAgICAgICAgICAgICAgICAgIHN0b3BwaW5nX3RvbGVyYW5jZSA9IDAuMDAwMDEsICMgQXJyw6p0ZXIgbG9yc3F1ZSBsYSBtw6l0cmlxdWUgc3RhZ25lIGRlIDAuMDAxJQ0KICAgICAgICAgICAgICAgICAgICAgIHNlZWQgPSAwKSAjIFJlcHJvZHVjdGlvbiBkZXMgcsOpc3VsdGF0cw0KICBoMm8uZG93bmxvYWRfcG9qbyh0ZW1wX21vZGVsLCAjIE1vZMOobGUgw6AgZW5yZWdpc3RyZXINCiAgICAgICAgICAgICAgICAgICAgcGF0aCA9IGZpbGVfaDJvLCAjIE/DuSBlbnJlZ2lzdHJlciBsZSBtb2TDqGxlID8NCiAgICAgICAgICAgICAgICAgICAgZ2V0X2phciA9IEZBTFNFKSAjIFBhcyBkZSBmaWNoaWVyIC5qYXINCiAgYWNjdXJhY3lbaSwgOV0gPC0gMSAtIG1pbih0ZW1wX21vZGVsQG1vZGVsJHNjb3JpbmdfaGlzdG9yeSR2YWxpZGF0aW9uX2NsYXNzaWZpY2F0aW9uX2Vycm9yLCBuYS5ybSA9IFRSVUUpDQogIA0KICAjIEVudHJhaW5lbWVudCBkdSByw6lzZWF1IGRlIG5ldXJvbmVzIMOgIGFyY2hpdGVjdHVyZSAzMng2ICsgUmVMVSAoaDJvKQ0KICB0ZW1wX21vZGVsIDwtIGgyb19ubl90cmFpbih0cmFpbiA9IHRyYWluaW5nX2gyb1tbaV1dLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0ZXN0ID0gdGVzdGluZ19oMm9bW2ldXSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbW9kZWxfaWQgPSBwYXN0ZTAoImgyb19ubl8zMng2X1JlTFVfIiwgc3ByaW50ZigiJTAyZCIsIGkpKSwgIyBOb20gZHUgbW9kw6hsZQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhY3RpdmF0aW9uID0gIlJlY3RpZmllciIsICMgUmVMVQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBoaWRkZW4gPSAzMikgIyBBcmNoaXRlY3R1cmUgMzJ4Ng0KICBoMm8uZG93bmxvYWRfcG9qbyh0ZW1wX21vZGVsLCAjIE1vZMOobGUgw6AgZW5yZWdpc3RyZXINCiAgICAgICAgICAgICAgICAgICAgcGF0aCA9IGZpbGVfaDJvLCAjIE/DuSBlbnJlZ2lzdHJlciBsZSBtb2TDqGxlID8NCiAgICAgICAgICAgICAgICAgICAgZ2V0X2phciA9IEZBTFNFKSAjIFBhcyBkZSBmaWNoaWVyIC5qYXINCiAgYWNjdXJhY3lbaSwgMTBdIDwtIDEgLSBtaW4odGVtcF9tb2RlbEBtb2RlbCRzY29yaW5nX2hpc3RvcnkkdmFsaWRhdGlvbl9jbGFzc2lmaWNhdGlvbl9lcnJvciwgbmEucm0gPSBUUlVFKQ0KICANCiAgIyBFbnRyYWluZW1lbnQgZHUgcsOpc2VhdSBkZSBuZXVyb25lcyDDoCBhcmNoaXRlY3R1cmUgMzJ4NiArIFRhbmggKGgybykNCiAgdGVtcF9tb2RlbCA8LSBoMm9fbm5fdHJhaW4odHJhaW4gPSB0cmFpbmluZ19oMm9bW2ldXSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGVzdCA9IHRlc3RpbmdfaDJvW1tpXV0sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1vZGVsX2lkID0gcGFzdGUwKCJoMm9fbm5fMzJ4Nl9UYW5oXyIsIHNwcmludGYoIiUwMmQiLCBpKSksICMgTm9tIGR1IG1vZMOobGUNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYWN0aXZhdGlvbiA9ICJUYW5oIiwgIyAiU2lnbW9pZGUiDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhpZGRlbiA9IDMyKSAjIEFyY2hpdGVjdHVyZSAzMng2DQogIGgyby5kb3dubG9hZF9wb2pvKHRlbXBfbW9kZWwsICMgTW9kw6hsZSDDoCBlbnJlZ2lzdHJlcg0KICAgICAgICAgICAgICAgICAgICBwYXRoID0gZmlsZV9oMm8sICMgT8O5IGVucmVnaXN0cmVyIGxlIG1vZMOobGUgPw0KICAgICAgICAgICAgICAgICAgICBnZXRfamFyID0gRkFMU0UpICMgUGFzIGRlIGZpY2hpZXIgLmphcg0KICBhY2N1cmFjeVtpLCAxMV0gPC0gMSAtIG1pbih0ZW1wX21vZGVsQG1vZGVsJHNjb3JpbmdfaGlzdG9yeSR2YWxpZGF0aW9uX2NsYXNzaWZpY2F0aW9uX2Vycm9yLCBuYS5ybSA9IFRSVUUpDQogIA0KICAjIEVudHJhaW5lbWVudCBkdSByw6lzZWF1IGRlIG5ldXJvbmVzIMOgIGFyY2hpdGVjdHVyZSAxNngxNng2ICsgUmVMVSAoaDJvKQ0KICB0ZW1wX21vZGVsIDwtIGgyb19ubl90cmFpbih0cmFpbiA9IHRyYWluaW5nX2gyb1tbaV1dLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0ZXN0ID0gdGVzdGluZ19oMm9bW2ldXSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbW9kZWxfaWQgPSBwYXN0ZTAoImgyb19ubl8xNngxNng2X1JlTFVfIiwgc3ByaW50ZigiJTAyZCIsIGkpKSwgIyBOb20gZHUgbW9kw6hsZQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhY3RpdmF0aW9uID0gIlJlY3RpZmllciIsICMgUmVMVQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBoaWRkZW4gPSBjKDE2LCAxNikpICMgQXJjaGl0ZWN0dXJlIDE2eDE2eDYNCiAgaDJvLmRvd25sb2FkX3Bvam8odGVtcF9tb2RlbCwgIyBNb2TDqGxlIMOgIGVucmVnaXN0cmVyDQogICAgICAgICAgICAgICAgICAgIHBhdGggPSBmaWxlX2gybywgIyBPw7kgZW5yZWdpc3RyZXIgbGUgbW9kw6hsZSA/DQogICAgICAgICAgICAgICAgICAgIGdldF9qYXIgPSBGQUxTRSkgIyBQYXMgZGUgZmljaGllciAuamFyDQogIGFjY3VyYWN5W2ksIDEyXSA8LSAxIC0gbWluKHRlbXBfbW9kZWxAbW9kZWwkc2NvcmluZ19oaXN0b3J5JHZhbGlkYXRpb25fY2xhc3NpZmljYXRpb25fZXJyb3IsIG5hLnJtID0gVFJVRSkNCiAgDQogICMgRW50cmFpbmVtZW50IGR1IHLDqXNlYXUgZGUgbmV1cm9uZXMgw6AgYXJjaGl0ZWN0dXJlIDE2eDE2eDYgKyBUYW5oIChoMm8pDQogIHRlbXBfbW9kZWwgPC0gaDJvX25uX3RyYWluKHRyYWluID0gdHJhaW5pbmdfaDJvW1tpXV0sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRlc3QgPSB0ZXN0aW5nX2gyb1tbaV1dLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtb2RlbF9pZCA9IHBhc3RlMCgiaDJvX25uXzE2eDE2eDZfVGFuaF8iLCBzcHJpbnRmKCIlMDJkIiwgaSkpLCAjIE5vbSBkdSBtb2TDqGxlDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFjdGl2YXRpb24gPSAiVGFuaCIsICMgIlNpZ21vaWRlIg0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBoaWRkZW4gPSBjKDE2LCAxNikpICMgQXJjaGl0ZWN0dXJlIDE2eDE2eDYNCiAgaDJvLmRvd25sb2FkX3Bvam8odGVtcF9tb2RlbCwgIyBNb2TDqGxlIMOgIGVucmVnaXN0cmVyDQogICAgICAgICAgICAgICAgICAgIHBhdGggPSBmaWxlX2gybywgIyBPw7kgZW5yZWdpc3RyZXIgbGUgbW9kw6hsZSA/DQogICAgICAgICAgICAgICAgICAgIGdldF9qYXIgPSBGQUxTRSkgIyBQYXMgZGUgZmljaGllciAuamFyDQogIGFjY3VyYWN5W2ksIDEzXSA8LSAxIC0gbWluKHRlbXBfbW9kZWxAbW9kZWwkc2NvcmluZ19oaXN0b3J5JHZhbGlkYXRpb25fY2xhc3NpZmljYXRpb25fZXJyb3IsIG5hLnJtID0gVFJVRSkNCiAgDQp9DQoNCiMgVGVtcHMgbsOpY2Vzc2FpcmUNCnRpbWluZyhDdXJyZW50VGltZSwgIkNyw6lhdGlvbiBldCDDqXZhbHVhdGlvbiBkZXMgZG91emUgbW9kw6hsZXMgZGUgYmVuY2htYXJrIGZpbmFsIikNCmBgYA0KDQojIyBBZmZpY2hhZ2UgZGVzIHLDqXN1bHRhdHMNCg0KTGVzIHLDqXN1bHRhdHMgZGUgbGEgcGVyZm9ybWFuY2UgZGVzIG1vZMOobGVzIGVudHJhaW7DqXMgc29udCBjaS1kZXNzb3VzLiBMZXMgcGVyZm9ybWFuY2VzIHNlbWJsZW50IGzDqWfDqHJlbWVudCBlbiBoYXVzc2UgKGRlIDIlIGVudmlyb24pLiBFbiByZXZhbmNoZSwgbGVzIHBlcmZvcm1hbmNlcyBzb250IHBsdXMgZmFpYmxlcyBzdXIgbG9yc3F1ZSBsZXMgZG9ubsOpZXMgc29udCB2YWxpZMOpZXMgc3VyIGxhIHNhbGxlIDEuDQoNCmBgYHtyIFJlc3VsdGF0czN9DQojIE1veWVubmUgZGVzIHLDqXN1bHRhdHMNCmZvciAoaSBpbiAyOjEzKSB7DQogIGFjY3VyYWN5WzEzLCBpXSA8LSBtZWFuKGFjY3VyYWN5WzE6NiwgaV0pDQogIGFjY3VyYWN5WzE0LCBpXSA8LSBtZWFuKGFjY3VyYWN5Wzc6OSwgaV0pDQogIGFjY3VyYWN5WzE1LCBpXSA8LSBtZWFuKGFjY3VyYWN5WzEwOjEyLCBpXSkNCiAgYWNjdXJhY3lbMTYsIGldIDwtIG1lYW4oYWNjdXJhY3lbMTM6MTUsIGldKQ0KfQ0KDQojIEVucmVnaXN0cmVtZW50IGRlcyBzY29yZXMNCmZ3cml0ZShhY2N1cmFjeSwgInNjb3Jlcy8zX21vZGVscy5jc3YiKQ0KDQojIEFmZmljaGFnZSBkZXMgcsOpc3VsdGF0cyBkYW5zIHVuIHRhYmxlYXUgaW50ZXJhY3RpZg0KdG9fcHJpbnQgPC0gZGF0YS50YWJsZSh0KGFjY3VyYWN5WzEzOjE2LCAtMV0pKSAjIFByw6lwYXJhdGlvbiBkZXMgZG9ubsOpZXMgw6AgbWV0dHJlIHN1ciB0YWJsZQ0KY29sbmFtZXModG9fcHJpbnQpIDwtIGMoIjEgY29udHJlIDEiLCAiMSBjb250cmUgMiIsICIyIGNvbnRyZSAxIiwgIk1veWVubmUiKSAjIFJlbWlzZSBkZXMgbm9tcyBkZXMgY29sb25uZXMNCnJvdy5uYW1lcyh0b19wcmludCkgPC0gY29sbmFtZXMoYWNjdXJhY3kpWy0xXSAjIFJlbWlzZSBkZXMgbm9tcyBkZXMgbGlnbmVzDQpkYXRhdGFibGUodG9fcHJpbnQsDQogICAgICAgICAgZmlsdGVyID0gInRvcCIsICMgRmlsdHJhZ2UgYXUtZGVzc3VzIGRlIGxhIHRhYmxlDQogICAgICAgICAgY2xhc3MgPSAiY2VsbC1ib3JkZXIgc3RyaXBlIiwgIyBDU1MNCiAgICAgICAgICBleHRlbnNpb25zID0gYygiQ29sUmVvcmRlciIsDQogICAgICAgICAgICAgICAgICAgICAgICAgIlJvd1Jlb3JkZXIiKSwgIyBSZW9yZG9ubmVyIG1hbnVlbGxlbWVudCDDoCBsYSBtYWluDQogICAgICAgICAgb3B0aW9ucyA9IGxpc3QocGFnZUxlbmd0aCA9IDEyLCAjIFBhZ2UgYWZmaWNoYW50IDEyIGxpZ25lcw0KICAgICAgICAgICAgICAgICAgICAgICAgIG9yZGVyID0gbGlzdChsaXN0KDQsICJkZXNjIikpLCAjIE9yZG9ubmVyIHBhciBkw6lmYXV0IHBhciBsJ2V4YWN0aXR1ZGUgbW95ZW5uZQ0KICAgICAgICAgICAgICAgICAgICAgICAgIGNvbFJlb3JkZXIgPSBUUlVFLCAjIFBsdWdpbg0KICAgICAgICAgICAgICAgICAgICAgICAgIHJvd1Jlb3JkZXIgPSBUUlVFKSkgJT4lICMgUGx1Z2luDQogIGZvcm1hdFN0eWxlKGMoIjEgY29udHJlIDEiLCAiMSBjb250cmUgMiIsICIyIGNvbnRyZSAxIiksDQogICAgICAgICAgICAgICAgICBiYWNrZ3JvdW5kID0gc3R5bGVDb2xvckJhcihjKDAsIDEpLCAnbGlnaHRncmVlbicpLCAjIENvdWxldXIgdmVydCBjbGFpciBwb3VyIGxlcyBtw6l0cmlxdWVzIHBhciBmb2xkDQogICAgICAgICAgICAgICAgICBiYWNrZ3JvdW5kU2l6ZSA9ICcxMDAlIDkwJScsDQogICAgICAgICAgICAgICAgICBiYWNrZ3JvdW5kUmVwZWF0ID0gJ25vLXJlcGVhdCcsDQogICAgICAgICAgICAgICAgICBiYWNrZ3JvdW5kUG9zaXRpb24gPSAnY2VudGVyJykgJT4lDQogIGZvcm1hdFN0eWxlKCJNb3llbm5lIiwNCiAgICAgICAgICAgICAgYmFja2dyb3VuZCA9IHN0eWxlQ29sb3JCYXIoYygwLCAxKSwgJ3BpbmsnKSwgIyBDb3VsZXVyIHJvc2UgcG91ciBsYSBtw6l0cmlxdWUgZGUgbW95ZW5uZQ0KICAgICAgICAgICAgICBiYWNrZ3JvdW5kU2l6ZSA9ICcxMDAlIDkwJScsDQogICAgICAgICAgICAgIGJhY2tncm91bmRSZXBlYXQgPSAnbm8tcmVwZWF0JywNCiAgICAgICAgICAgICAgYmFja2dyb3VuZFBvc2l0aW9uID0gJ2NlbnRlcicpICU+JQ0KICBmb3JtYXRQZXJjZW50YWdlKGNvbHVtbnMgPSBjKCIxIGNvbnRyZSAxIiwgIjEgY29udHJlIDIiLCAiMiBjb250cmUgMSIpLA0KICAgICAgICAgICAgICBkaWdpdHMgPSA4KSAlPiUNCiAgZm9ybWF0UGVyY2VudGFnZShjb2x1bW5zID0gIk1veWVubmUiLA0KICAgICAgICAgICAgICBkaWdpdHMgPSA4KQ0KDQojIEFmZmljaGFnZSBkZXMgcsOpc3VsdGF0cyBkYW5zIHVuIHRhYmxlYXUgc3RhdGlxdWUNCmZvcm1hdHRhYmxlKGFjY3VyYWN5WywgYygxLCAyOjUpXSwgbGlzdChmb3JtYXR0YWJsZTo6YXJlYShjb2wgPSB4Z2JfTGluZWFyTW9kZWw6eGdiX0dyYWRpZW50Qm9vc3RpbmcpIH4gY29sb3JfYmFyKCJvcmFuZ2UiKSkpDQpmb3JtYXR0YWJsZShhY2N1cmFjeVssIGMoMSwgNjo5KV0sIGxpc3QoZm9ybWF0dGFibGU6OmFyZWEoY29sID0gaDJvX0xpbmVhck1vZGVsOmgyb19HcmFkaWVudEJvb3N0aW5nKSB+IGNvbG9yX2JhcigiY3lhbiIpKSkNCmZvcm1hdHRhYmxlKGFjY3VyYWN5WywgYygxLCAxMDoxMyldLCBsaXN0KGZvcm1hdHRhYmxlOjphcmVhKGNvbCA9IGgyb19OTl8zMng2X1JlTFU6aDJvX05OXzE2eDE2eDZfU29mdCkgfiBjb2xvcl9iYXIoInllbGxvdyIpKSkNCmBgYA0KDQojIyBBbmFseXNlIGR1IG1vZMOobGUgZGUgcsOpZ3Jlc3Npb24gbG9naXN0aXF1ZQ0KDQpMZXMgY29lZmZpY2llbnRzIGFmZmVjdMOpcyBzZWxvbiBsZXMgw6ljaGFudGlsbG9ucyBkJ2VudHJhaW5lbWVudCBzZW1ibGVudCBtb2lucyBzdGFibGVzIHF1J2F1cGFyYXZhbnQsIHBldXQgw6p0cmUgdW4gcHJvYmzDqG1lIGQnw6ljaGVsbGUgKG91IGRlIFZJRiBlbnRyZSBsZXMgdmFyaWFibGVzKS4NCg0KT24gcmVtYXJxdWUgcXVlIGxlIGNoZW1pbiAyIGVzdCBiaWVuIG1pZXV4IGNsYXNzw6kgcXUnYXVwYXJhdmFudCwgbWFpcyBxdWUgbGEgc2FsbGUgMyBlc3QgdG91am91cnMgZGlmZmljaWxlIMOgIGNsYXNzZXIgKG9uIGVzdCBwYXNzw6kgZGUgMTIlIGQnZXhhY3RpdHVkZSDDoCAyNCUsIGNlIHF1aSBlc3QgZGV1eCBmb2lzIHBsdXMgcHLDqWNpcykuDQoNCkVuIHJldmFuY2hlLCBsYSBzYWxsZSA0IHNlbWJsZSBzb3VmZmlyICg0MCUgZCdleGFjdGl0dWRlKSwgY2UgcXVpIG4nw6l0YWlzIHBhcyBsZSBjYXMgYXZlYyBsJ2FwcHJveGltYXRpb24gZGVzIHNpZ25hdXggZGVzIGFuY3JlcyAoNTUlIGQnZXhhY3RpdHVkZSkuDQoNCmBgYHtyIEFuYWx5c2VMb2dpc3RpcXVlMiwgd2FybmluZ3MgPSBGQUxTRX0NCiMgQ29tcHRldXIgZGUgdGVtcHMNCkN1cnJlbnRUaW1lIDwtIHRpbWVyKCkgIyBDaHVuayBQcsOpcGFyYXRpb24gZGUgbCdhbmFseXNlIGR1IG1vZMOobGUgZGUgcsOpZ3Jlc3Npb24gbG9naXN0aXF1ZSBmaW5hbA0KDQojIFByw6ktaW5pdGlhbGlzYXRpb24gZGVzIHZhcmlhYmxlcw0KcHJlZGljdGVkVmFsdWVzIDwtIG1hdHJpeChucm93ID0gMzE0LCBuY29sID0gNikNCmV2b2x1dGlvbiA8LSBsaXN0KCkNCnRlbXBfZHQgPC0gbGlzdCgpDQp0ZW1wX21lYW5zIDwtIGRhdGEuZnJhbWUoRmVhdHVyZSA9IGMocGFzdGUwKHJlcChjKHBhc3RlMCgiQ29lZiIsIDE6NCksIHBhc3RlMCgiUsOpc2kiLCAxOjQpKSwgNCksIHBhc3RlMCgiXyIsIGludmVyc2UucmxlKGxpc3QobGVuZ3RocyA9IHJlcCg4LCA0KSwgdmFsdWVzID0gMTo0KSkpKSwgcGFzdGUwKCJQb3NJbml0aWFsZV8iLCAxOjQpKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICBGb2xkXzEgPSBudW1lcmljKDM2KSwNCiAgICAgICAgICAgICAgICAgICAgICAgICBGb2xkXzIgPSBudW1lcmljKDM2KSwNCiAgICAgICAgICAgICAgICAgICAgICAgICBGb2xkXzMgPSBudW1lcmljKDM2KSwNCiAgICAgICAgICAgICAgICAgICAgICAgICBGb2xkX01lYW4gPSBudW1lcmljKDM2KSwNCiAgICAgICAgICAgICAgICAgICAgICAgICBGZWF0dXJlX01lYW4gPSBudW1lcmljKDM2KSwNCiAgICAgICAgICAgICAgICAgICAgICAgICBGZWF0dXJlX1NEID0gbnVtZXJpYygzNikpDQoNCiMgQm91Y2xlIGQnZW50cmFpbmVtZW50IDIgY29udHJlIDENCmZvciAoaSBpbiAxMDoxMikgew0KICANCiAgIyBFbnRyYWluZW1lbnQgZCd1biBtb2TDqGxlIGxpbsOpYWlyZQ0KICB0ZW1wX21vZGVsIDwtIHhnYi50cmFpbihkYXRhID0gdHJhaW5pbmdfeGdiW1tpXV0sDQogICAgICAgICAgICAgICAgICAgICAgICAgIG51bV9jbGFzcyA9IDYsICMgQ2xhc3NpZmljYXRpb24gw6AgNiBjbGFzc2VzDQogICAgICAgICAgICAgICAgICAgICAgICAgIG50aHJlYWQgPSAxLCAjIDEgY29ldXIgdXRpbGlzw6kNCiAgICAgICAgICAgICAgICAgICAgICAgICAgbnJvdW5kcyA9IDEwMDAwMDAsICMgTm9tYnJlIGQnaXTDqXJhdGlvbnMgZGUgYm9vc3RpbmcNCiAgICAgICAgICAgICAgICAgICAgICAgICAgZXRhID0gMC4xMCwgIyBTaHJpbmthZ2UgcG91ciBsZSBib29zdGluZw0KICAgICAgICAgICAgICAgICAgICAgICAgICBib29zdGVyID0gImdibGluZWFyIiwgIyBUeXBlIGQnZW50cmFpbmVtZW50IDogbGluw6lhaXJlIG91IG5vbi1saW7DqWFpcmUNCiAgICAgICAgICAgICAgICAgICAgICAgICAgb2JqZWN0aXZlID0gIm11bHRpOnNvZnRwcm9iIiwgIyBHcmFkaWVudC9IZXNzaWFuIHBvdXIgbCdvcHRpbWlzYXRpb24gcGFyIEdyYWRpZW50IERlc2NlbnQNCiAgICAgICAgICAgICAgICAgICAgICAgICAgZXZhbF9tZXRyaWMgPSAibWVycm9yIiwgIyBJbmV4YWN0aXR1ZGUgZGUgbGEgY2xhc3NpZmljYXRpb24NCiAgICAgICAgICAgICAgICAgICAgICAgICAgbWF4aW1pemUgPSBGQUxTRSwgIyBNaW5pbWlzYXRpb24gZGUgbCdlcnJldXINCiAgICAgICAgICAgICAgICAgICAgICAgICAgZWFybHlfc3RvcHBpbmdfcm91bmRzID0gMTAwLCAjIEFycsOqdCBhcHLDqHMgMTAwIGl0w6lyYXRpb25zIHNhbnMgYW3DqWxpb3JhdGlvbiBkZSBsYSBtw6l0cmlxdWUNCiAgICAgICAgICAgICAgICAgICAgICAgICAgdmVyYm9zZSA9IEZBTFNFLCAjIFNhbnMgcHJpbnQgZGVzIGl0w6lyYXRpb25zDQogICAgICAgICAgICAgICAgICAgICAgICAgIHdhdGNobGlzdCA9IGxpc3QodGVzdCA9IHRlc3RpbmdfeGdiW1tpXV0pLCAjIEVzdGltYXRpb24gc3VyIGxlcyBkb25uw6llcyBkZSB0ZXN0DQogICAgICAgICAgICAgICAgICAgICAgICAgIGNhbGxiYWNrcyA9IGxpc3QoY2IuZXZhbHVhdGlvbi5sb2coKSkpICMgTG9nZ2luZyBkZXMgZG9ubsOpZXMgZCdlbnRyYWluZW1lbnQgcG91ciBwb3V2b2lyIHLDqWN1cMOpcmVyIGxlcyBtw6l0cmlxdWVzDQogIA0KICAjIEVucmVnaXN0cmVtZW50IGR1IGxvZw0KICBldm9sdXRpb25bW2kgLSA5XV0gPC0gY2JpbmQodGVtcF9tb2RlbCRldmFsdWF0aW9uX2xvZywgRm9sZCA9IHJlcCgxMyAtIGksIHRlbXBfbW9kZWwkbml0ZXIpKQ0KICANCiAgIyBFbnRyYWluZW1lbnQgZHUgbWVpbGxldXIgbW9kw6hsZSAob2J0ZW50aW9uIGRlcyBtZWlsbGV1cnMgY29lZmZpY2llbnRzKQ0KICB0ZW1wX21vZGVsIDwtIHhnYi50cmFpbihkYXRhID0gdHJhaW5pbmdfeGdiW1tpXV0sDQogICAgICAgICAgICAgICAgICAgICAgICAgIG51bV9jbGFzcyA9IDYsICMgQ2xhc3NpZmljYXRpb24gw6AgNiBjbGFzc2VzDQogICAgICAgICAgICAgICAgICAgICAgICAgIG50aHJlYWQgPSAxLCAjIDEgY29ldXIgdXRpbGlzw6kNCiAgICAgICAgICAgICAgICAgICAgICAgICAgbnJvdW5kcyA9IHRlbXBfbW9kZWwkYmVzdF9pdGVyYXRpb24sICMgTm9tYnJlIGQnaXTDqXJhdGlvbnMgZGUgYm9vc3RpbmcNCiAgICAgICAgICAgICAgICAgICAgICAgICAgZXRhID0gMC4xMCwgIyBTaHJpbmthZ2UgcG91ciBsZSBib29zdGluZw0KICAgICAgICAgICAgICAgICAgICAgICAgICBib29zdGVyID0gImdibGluZWFyIiwgIyBUeXBlIGQnZW50cmFpbmVtZW50IDogbGluw6lhaXJlIG91IG5vbi1saW7DqWFpcmUNCiAgICAgICAgICAgICAgICAgICAgICAgICAgb2JqZWN0aXZlID0gIm11bHRpOnNvZnRwcm9iIiwgIyBHcmFkaWVudC9IZXNzaWFuIHBvdXIgbCdvcHRpbWlzYXRpb24gcGFyIEdyYWRpZW50IERlc2NlbnQNCiAgICAgICAgICAgICAgICAgICAgICAgICAgZXZhbF9tZXRyaWMgPSAibWVycm9yIiwgIyBJbmV4YWN0aXR1ZGUgZGUgbGEgY2xhc3NpZmljYXRpb24NCiAgICAgICAgICAgICAgICAgICAgICAgICAgbWF4aW1pemUgPSBGQUxTRSwgIyBNaW5pbWlzYXRpb24gZGUgbCdlcnJldXINCiAgICAgICAgICAgICAgICAgICAgICAgICAgZWFybHlfc3RvcHBpbmdfcm91bmRzID0gOTk5OTksICMgU2FucyBhcnLDqnQNCiAgICAgICAgICAgICAgICAgICAgICAgICAgdmVyYm9zZSA9IEZBTFNFLCAjIFNhbnMgcHJpbnQgZGVzIGl0w6lyYXRpb25zDQogICAgICAgICAgICAgICAgICAgICAgICAgIHdhdGNobGlzdCA9IGxpc3QodGVzdCA9IHRlc3RpbmdfeGdiW1tpXV0pLCAjIEVzdGltYXRpb24gc3VyIGxlcyBkb25uw6llcyBkZSB0ZXN0DQogICAgICAgICAgICAgICAgICAgICAgICAgIGNhbGxiYWNrcyA9IGxpc3QoY2IuZXZhbHVhdGlvbi5sb2coKSkpICMgTG9nZ2luZyBkZXMgZG9ubsOpZXMgZCdlbnRyYWluZW1lbnQgcG91ciBwb3V2b2lyIHLDqWN1cMOpcmVyIGxlcyBtw6l0cmlxdWVzDQogIA0KICAjIFByw6lkaWN0aW9uIGR1IG1vZMOobGUgbGluw6lhaXJlDQogIHByZWRpY3RlZFZhbHVlc1tmb2xkc190ZXN0W1tpXV0sIF0gPC0gdChtYXRyaXgocHJlZGljdCh0ZW1wX21vZGVsLCB0ZXN0aW5nX3hnYltbaV1dLCBudHJlZWxpbWl0ID0gMCksIG5yb3cgPSA2KSkNCiAgDQogICMgQ2FsY3VsIGV0IGZvcm1hdHRhZ2UgZGUgbCdpbXBvcnRhbmNlIGRlcyB2YXJpYWJsZXMNCiAgdGVtcF9pbXBvcnRhbmNlIDwtIGRhdGEudGFibGUoRmVhdHVyZSA9IHRlbXBfbWVhbnNbWyJGZWF0dXJlIl1dLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtYXRyaXgoeGdiLmltcG9ydGFuY2UobW9kZWwgPSB0ZW1wX21vZGVsKSRXZWlnaHQsIG5jb2wgPSA2KSkNCiAgY29sbmFtZXModGVtcF9pbXBvcnRhbmNlKSA8LSBjKCJGZWF0dXJlIiwgcGFzdGUwKCJMYWJlbF8iLCAxOjYpKQ0KICB0ZW1wX2ltcG9ydGFuY2VbWyJTaWduIl1dIDwtIHBhc3RlMChpZmVsc2UodGVtcF9pbXBvcnRhbmNlW1syXV0gPj0gMCwgIisiLCAiLSIpLCBpZmVsc2UodGVtcF9pbXBvcnRhbmNlW1szXV0gPj0gMCwgIisiLCAiLSIpLCBpZmVsc2UodGVtcF9pbXBvcnRhbmNlW1s0XV0gPj0gMCwgIisiLCAiLSIpLCBpZmVsc2UodGVtcF9pbXBvcnRhbmNlW1s1XV0gPj0gMCwgIisiLCAiLSIpLCBpZmVsc2UodGVtcF9pbXBvcnRhbmNlW1s2XV0gPj0gMCwgIisiLCAiLSIpLCBpZmVsc2UodGVtcF9pbXBvcnRhbmNlW1s3XV0gPj0gMCwgIisiLCAiLSIpKQ0KICB0ZW1wX2ltcG9ydGFuY2VbLCAyOjddIDwtIGFicyh0ZW1wX2ltcG9ydGFuY2VbLCAyOjcsIHdpdGggPSBGQUxTRV0pDQogIHRlbXBfbWVhbnNbWzE0IC0gaV1dIDwtIHJvd01lYW5zKHRlbXBfaW1wb3J0YW5jZVssIDI6Nywgd2l0aCA9IEZBTFNFXSkNCiAgdGVtcF9pbXBvcnRhbmNlW1twYXN0ZTAoIkZvbGRfIiwgMTMgLSBpLCAiX01lYW4iKV1dIDwtIHRlbXBfbWVhbnNbWzE0IC0gaV1dDQogIA0KICAjIEVucmVnaXN0cmVtZW50IHNvdXMgZm9ybWUgZGUgdGFibGVhdSBpbnRlcmFjdGlmDQogIHRlbXBfZHRbW2kgLSA5XV0gPC0gZGF0YXRhYmxlKHRlbXBfaW1wb3J0YW5jZSwNCiAgICAgICAgZmlsdGVyID0gInRvcCIsICMgRmlsdHJhZ2UgYXUtZGVzc3VzIGRlIGxhIHRhYmxlDQogICAgICAgIGNsYXNzID0gImNlbGwtYm9yZGVyIHN0cmlwZSIsICMgQ1NTDQogICAgICAgIGV4dGVuc2lvbnMgPSBjKCJDb2xSZW9yZGVyIiwNCiAgICAgICAgICAgICAgICAgICAgICAgIlJvd1Jlb3JkZXIiKSwgIyBSZW9yZG9ubmVyIG1hbnVlbGxlbWVudCDDoCBsYSBtYWluDQogICAgICAgIG9wdGlvbnMgPSBsaXN0KG9yZGVyID0gbGlzdChsaXN0KDksICJkZXNjIikpLCAjIE9yZG9ubmVyIHBhciBkw6lmYXV0IHBhciBsZXMgZmFjdGV1cnMgYXlhbnQgbGUgcG9pZHMgbGUgcGx1cyBncm9zDQogICAgICAgICAgICAgICAgICAgICAgIGNvbFJlb3JkZXIgPSBUUlVFLCAjIFBsdWdpbg0KICAgICAgICAgICAgICAgICAgICAgICByb3dSZW9yZGVyID0gVFJVRSkpICU+JSAjIFBsdWdpbg0KICBmb3JtYXRTdHlsZShwYXN0ZTAoIkxhYmVsXyIsIDE6NiksDQogICAgICAgICAgICAgICAgICBiYWNrZ3JvdW5kID0gc3R5bGVDb2xvckJhcihyYW5nZSh0ZW1wX2ltcG9ydGFuY2VbLCAyOjcsIHdpdGggPSBGQUxTRV0pLCAnbGlnaHRibHVlJyksICMgQ291bGV1ciBibGV1ZSBwb3VyIGxlIGNvZWZmaWNpZW50DQogICAgICAgICAgICAgICAgICBiYWNrZ3JvdW5kU2l6ZSA9ICcxMDAlIDkwJScsDQogICAgICAgICAgICAgICAgICBiYWNrZ3JvdW5kUmVwZWF0ID0gJ25vLXJlcGVhdCcsDQogICAgICAgICAgICAgICAgICBiYWNrZ3JvdW5kUG9zaXRpb24gPSAnY2VudGVyJykgJT4lDQogIGZvcm1hdFN0eWxlKHBhc3RlMCgiRm9sZF8iLCAxMyAtIGksICJfTWVhbiIpLA0KICAgICAgICAgICAgICBiYWNrZ3JvdW5kID0gc3R5bGVDb2xvckJhcihyYW5nZSh0ZW1wX2ltcG9ydGFuY2VbWzldXSksICdwaW5rJyksICMgQ291bGV1ciByb3NlIHBvdXIgbGEgbcOpdHJpcXVlIGRlIG1veWVubmUNCiAgICAgICAgICAgICAgYmFja2dyb3VuZFNpemUgPSAnMTAwJSA5MCUnLA0KICAgICAgICAgICAgICBiYWNrZ3JvdW5kUmVwZWF0ID0gJ25vLXJlcGVhdCcsDQogICAgICAgICAgICAgIGJhY2tncm91bmRQb3NpdGlvbiA9ICdjZW50ZXInKSAlPiUNCiAgZm9ybWF0Um91bmQoY29sdW1ucyA9IGMocGFzdGUwKCJMYWJlbF8iLCAxOjYpLCBwYXN0ZTAoIkZvbGRfIiwgMTMgLSBpLCAiX01lYW4iKSksDQogICAgICAgICAgICAgIGRpZ2l0cyA9IDYpDQogIA0KfQ0KDQojIENhbGN1bCBkdSBwb2lkcyBtb3llbiBhZmZlY3TDqSDDoCBjaGFxdWUgZmVhdHVyZQ0KdGVtcF9tZWFuc1tbNV1dIDwtIHJvd01lYW5zKHRlbXBfbWVhbnNbLCAyOjRdKSAjIFBvaWRzIG1veWVuDQp0ZW1wX21lYW5zW1s2XV0gPC0gYXBwbHkobWluaV9sbSwgMiwgZnVuY3Rpb24oeCkge21lYW4oeCl9KSAjIE1veWVubmUgZGUgbGEgZmVhdHVyZSBkYW5zIGxlcyBkb25uw6llcw0KdGVtcF9tZWFuc1tbN11dIDwtIGFwcGx5KG1pbmlfbG0sIDIsIGZ1bmN0aW9uKHgpIHtzZCh4KX0pICMgRWNhcnQtdHlwZSBkZSBsYSBmZWF0dXJlIGRhbnMgbGVzIGRvbm7DqWVzDQoNCiMgUHLDqXByYXRpb24gZHUgdGFibGVhdSBpbnRlcmFjdGlmIHN1ciBsZXMgcG9pZHMgbW95ZW5zIGFncsOpZ8Opcw0KdGVtcF9kdFtbNF1dIDwtIGRhdGF0YWJsZSh0ZW1wX21lYW5zLA0KICAgICAgICBmaWx0ZXIgPSAidG9wIiwgIyBGaWx0cmFnZSBhdS1kZXNzdXMgZGUgbGEgdGFibGUNCiAgICAgICAgY2xhc3MgPSAiY2VsbC1ib3JkZXIgc3RyaXBlIiwgIyBDU1MNCiAgICAgICAgZXh0ZW5zaW9ucyA9IGMoIkNvbFJlb3JkZXIiLA0KICAgICAgICAgICAgICAgICAgICAgICAiUm93UmVvcmRlciIpLCAjIFJlb3Jkb25uZXIgbWFudWVsbGVtZW50IMOgIGxhIG1haW4NCiAgICAgICAgb3B0aW9ucyA9IGxpc3Qob3JkZXIgPSBsaXN0KGxpc3QoNSwgImRlc2MiKSksICMgT3Jkb25uZXIgcGFyIGTDqWZhdXQgcGFyIGxlcyBmYWN0ZXVycyBheWFudCBsZSBwb2lkcyBsZSBwbHVzIGdyb3MgZW4gbW95ZW5uZQ0KICAgICAgICAgICAgICAgICAgICAgICBjb2xSZW9yZGVyID0gVFJVRSwgIyBQbHVnaW4NCiAgICAgICAgICAgICAgICAgICAgICAgcm93UmVvcmRlciA9IFRSVUUpKSAlPiUgIyBQbHVnaW4NCiAgZm9ybWF0U3R5bGUocGFzdGUwKCJGb2xkXyIsIDE6MyksDQogICAgICAgICAgICAgICAgICBiYWNrZ3JvdW5kID0gc3R5bGVDb2xvckJhcihyYW5nZSh0ZW1wX21lYW5zWywgMjo0XSksICdsaWdodGJsdWUnKSwgIyBDb3VsZXVyIGJsZXVlIHBvdXIgbGUgY29lZmZpY2llbnQNCiAgICAgICAgICAgICAgICAgIGJhY2tncm91bmRTaXplID0gJzEwMCUgOTAlJywNCiAgICAgICAgICAgICAgICAgIGJhY2tncm91bmRSZXBlYXQgPSAnbm8tcmVwZWF0JywNCiAgICAgICAgICAgICAgICAgIGJhY2tncm91bmRQb3NpdGlvbiA9ICdjZW50ZXInKSAlPiUNCiAgZm9ybWF0U3R5bGUoIkZvbGRfTWVhbiIsDQogICAgICAgICAgICAgIGJhY2tncm91bmQgPSBzdHlsZUNvbG9yQmFyKHJhbmdlKHRlbXBfbWVhbnNbWzVdXSksICdwaW5rJyksICMgQ291bGV1ciByb3NlIHBvdXIgbGEgbcOpdHJpcXVlIGRlIG1veWVubmUNCiAgICAgICAgICAgICAgYmFja2dyb3VuZFNpemUgPSAnMTAwJSA5MCUnLA0KICAgICAgICAgICAgICBiYWNrZ3JvdW5kUmVwZWF0ID0gJ25vLXJlcGVhdCcsDQogICAgICAgICAgICAgIGJhY2tncm91bmRQb3NpdGlvbiA9ICdjZW50ZXInKSAlPiUNCiAgZm9ybWF0U3R5bGUoIkZlYXR1cmVfTWVhbiIsDQogICAgICAgICAgICAgIGJhY2tncm91bmQgPSBzdHlsZUNvbG9yQmFyKHJhbmdlKHRlbXBfbWVhbnNbWzZdXSksICdsaWdodGdyZWVuJyksICMgQ291bGV1ciB2ZXJ0ZSBwb3VyIGxhIG1veWVubmUgZGVzIGZlYXR1cmVzDQogICAgICAgICAgICAgIGJhY2tncm91bmRTaXplID0gJzEwMCUgOTAlJywNCiAgICAgICAgICAgICAgYmFja2dyb3VuZFJlcGVhdCA9ICduby1yZXBlYXQnLA0KICAgICAgICAgICAgICBiYWNrZ3JvdW5kUG9zaXRpb24gPSAnY2VudGVyJykgJT4lDQogIGZvcm1hdFN0eWxlKCJGZWF0dXJlX1NEIiwNCiAgICAgICAgICAgICAgYmFja2dyb3VuZCA9IHN0eWxlQ29sb3JCYXIocmFuZ2UodGVtcF9tZWFuc1tbN11dKSwgJ29yYW5nZScpLCAjIENvdWxldXIgdmVydGUgcG91ciBsJ8OpY2FydC10eXBlIGRlcyBmZWF0dXJlcw0KICAgICAgICAgICAgICBiYWNrZ3JvdW5kU2l6ZSA9ICcxMDAlIDkwJScsDQogICAgICAgICAgICAgIGJhY2tncm91bmRSZXBlYXQgPSAnbm8tcmVwZWF0JywNCiAgICAgICAgICAgICAgYmFja2dyb3VuZFBvc2l0aW9uID0gJ2NlbnRlcicpICU+JQ0KICBmb3JtYXRSb3VuZChjb2x1bW5zID0gYyhwYXN0ZTAoIkZvbGRfIiwgMTozKSwgIkZvbGRfTWVhbiIsICJGZWF0dXJlX01lYW4iLCAiRmVhdHVyZV9TRCIpLA0KICAgICAgICAgICAgICBkaWdpdHMgPSA2KQ0KDQojIETDqXBpdm90YWdlIGR1IGxvZw0KZXZvbHV0aW9uIDwtIHJiaW5kbGlzdChldm9sdXRpb24pDQpjb2xuYW1lcyhldm9sdXRpb24pIDwtIGMoIkl0ZXJhdGlvbiIsICJFeGFjdGl0dWRlIiwgIkZvbGQiKQ0KZXZvbHV0aW9uJEV4YWN0aXR1ZGUgPC0gMSAtIGV2b2x1dGlvbiRFeGFjdGl0dWRlDQpldm9sdXRpb24kRm9sZCA8LSBhcy5mYWN0b3IoZXZvbHV0aW9uJEZvbGQpDQoNCiMgUHLDqWRpY3Rpb24gw6AgcGFydGlyIGRlcyBwcm9iYWJpbGl0w6lzDQpwcmVkaWN0ZWRMYWJlbCA8LSBkYXRhLmZyYW1lKExhYmVsID0gZ3JvdXBfcGF0aCRwYXRoX0lELCBQcmVkaWN0aW9uID0gYXBwbHkocHJlZGljdGVkVmFsdWVzLCAxLCBmdW5jdGlvbih4KSB7d2hpY2gubWF4KHgpfSkpDQoNCnRpbWluZyhDdXJyZW50VGltZSwgIlByw6lwYXJhdGlvbiBkZSBsJ2FuYWx5c2UgZHUgbW9kw6hsZSBkZSByw6lncmVzc2lvbiBsb2dpc3RpcXVlIGZpbmFsIikNCg0KIyBBZmZpY2hhZ2UgZGUgbCfDqXZvbHV0aW9uIGRlIGxhIHBlcmZvcm1hbmNlIGR1IG1vZMOobGUgc2Vsb24gbGUgbm9tYnJlIGQnaXTDqXJhdGlvbiwgc291cyBmb3JtZSBkZSBwbG90IGludGVyYWN0aWYNCmdncGxvdGx5KGdncGxvdChkYXRhID0gZXZvbHV0aW9uLCBhZXNfc3RyaW5nKHggPSAiSXRlcmF0aW9uIiwgeSA9ICJFeGFjdGl0dWRlIiwgZ3JvdXAgPSAiRm9sZCIsIGNvbG9yID0gIkZvbGQiKSkgKyBnZW9tX2xpbmUoKSArIGdlb21fcG9pbnQoKSArIHNjYWxlX2NvbG9yX2JyZXdlcihwYWxldHRlID0gIlNldDIiKSArIHRoZW1lX2J3KCkgKyBsYWJzKHRpdGxlID0gIkV2b2x1dGlvbiBkZSBsJ2V4YWN0aXR1ZGUgcGFyIHJhcHBvcnQgYXUgbm9tYnJlIGQnaXTDqXJhdGlvbnMgZCdlbnRyYWluZW1lbnQiKSwgd2lkdGggPSA5NjAsIGhlaWdodCA9IDcyMCkNCg0KIyBBZmZpY2hhZ2UgZGUgbGEgbWF0cmljZSBkZSBjb25mdXNpb24gc291cyBmb3JtZSBkZSBwbG90IGludGVyYWN0aWYNCmNvbmZ1c2lvbl9tYXQgPC0gZXhwYW5kLmdyaWQoTGFiZWwgPSAxOjYsIFByZWRpY3Rpb24gPSAxOjYpDQpjb25mdXNpb25fbWF0IDwtIG1lcmdlKGNvbmZ1c2lvbl9tYXQsIGRhdGEudGFibGUocHJlZGljdGVkTGFiZWwpWywgbGlzdChGcmVxID0gc3VtKC5OKSksIGJ5ID0gbGlzdChMYWJlbCwgUHJlZGljdGlvbildLCBieSA9IGMoIkxhYmVsIiwgIlByZWRpY3Rpb24iKSwgYWxsLnggPSBUUlVFKQ0KY29uZnVzaW9uX21hdFtbIkZyZXEiXV1baXMubmEoY29uZnVzaW9uX21hdFtbIkZyZXEiXV0pXSA8LSAwDQpnZ3Bsb3RseShnZ3Bsb3QoKSArIGdlb21fcmVjdChkYXRhID0gZGF0YS5mcmFtZShjZW50ID0gMTo2KSwgc2l6ZSA9IDIsIGZpbGwgPSBOQSwgY29sb3VyID0gImJsYWNrIiwgYWVzKHhtaW4gPSBjZW50IC0gMC41LCB4bWF4ID0gY2VudCArIDAuNSwgeW1pbiA9IGNlbnQgLSAwLjUsIHltYXggPSBjZW50ICsgMC41KSkgKyBnZW9tX3RpbGUoZGF0YSA9IGNvbmZ1c2lvbl9tYXQsIGFlc19zdHJpbmcoeCA9ICJMYWJlbCIsIHkgPSAiUHJlZGljdGlvbiIsIGZpbGwgPSAiRnJlcSIpKSArIGdlb21fdGV4dChkYXRhID0gY29uZnVzaW9uX21hdCwgYWVzX3N0cmluZyh4ID0gIkxhYmVsIiwgeSA9ICJQcmVkaWN0aW9uIiwgbGFiZWwgPSAiRnJlcSIpKSArIHNjYWxlX3hfZGlzY3JldGUobmFtZSA9ICJUcmFqZWN0b2lyZSBSw6llbGxlIikgKyBzY2FsZV95X2Rpc2NyZXRlKG5hbWUgPSAiVHJhamVjdG9pcmUgUHLDqWRpdGUiKSArIHNjYWxlX2ZpbGxfZ3JhZGllbnRuKGNvbG91cnMgPSByZXYoYnJld2VyLnBhbF9leHRlbmRlZCgzLCAiUGlZRyIpKSkgKyBsYWJzKHRpdGxlID0gIk1hdHJpY2UgZGUgQ29uZnVzaW9uIGRlIGxhIFRyYWplY3RvaXJlIiwgZmlsbCA9ICJGcsOpcXVlbmNlIiksIHdpZHRoID0gOTYwLCBoZWlnaHQgPSA3MjApDQoNCiMgQWZmaWNoYWdlIGRlcyB0YWJsZXMgw6AgbGEgZmluIGNhciBsZSBmb3JtYXR0YWdlIHBvc3PDqGRlIHVuIGJ1ZyBpbmjDqXJlbnQgbG9yc3F1J29uIGEgcGx1c2lldXJzIGRhdGF0YWJsZXMgKERUKSBkYW5zIGxlIG3Dqm1lIGNodW5rDQojIGh0bWx0b29sczo6dGFnTGlzdCh0ZW1wX2R0W1szXV0sIHRlbXBfZHRbWzJdXSwgdGVtcF9kdFtbMV1dLCB0ZW1wX2R0W1s0XV0pDQpgYGANCg0KYGBge3IgVGVtcDUsIGVjaG89RkFMU0V9DQp0ZW1wX2R0W1szXV0gIyBDb2VmZmljaWVudHMgY29udHJlIGxhIHNhbGxlIDMNCmBgYA0KDQpgYGB7ciBUZW1wNiwgZWNobz1GQUxTRX0NCnRlbXBfZHRbWzJdXSAjIENvZWZmaWNpZW50cyBjb250cmUgbGEgc2FsbGUgMg0KYGBgDQoNCmBgYHtyIFRlbXA3LCBlY2hvPUZBTFNFfQ0KdGVtcF9kdFtbMV1dICMgQ29lZmZpY2llbnRzIGNvbnRyZSBsYSBzYWxsZSAxDQpgYGANCg0KYGBge3IgVGVtcDgsIGVjaG89RkFMU0V9DQp0ZW1wX2R0W1s0XV0gIyBDb2VmZmljaWVudHMgYWdyw6lnw6lzDQpgYGANCg0KIyBPcHRpbWlzYXRpb24gZGUgbGEgcsOpZ3Jlc3Npb24gbG9naXN0aXF1ZQ0KDQpJbCBlc3QgdG91dCDDoCBmYWl0IHBvc3NpYmxlIGQnb3B0aW1pc2VyIGxhIHLDqWdyZXNzaW9uIGxvZ2lzdGlxdWUsIHBhciB0cm9pcyBjaGVtaW5zIDoNCg0KLSBPcHRpbWlzZXIgbGVzIGh5cGVycGFyYW3DqHRyZXMNCi0gU8OpbGVjdGlvbm5lciBsZSBtZWlsbGV1ciBzdWJzZXQgZGUgZmVhdHVyZXMgw6AgdXRpbGlzZXINCi0gVXRpbGlzZXIgZGUgbWVpbGxldXJlcyBmZWF0dXJlcw0KDQpQYXIgbWFucXVlIGRlIHRlbXBzLCBub3VzIG5lIHRyYXZhaWxsZXJvbnMgcGFzIHN1ciBsJ8OpbGFib3JhdGlvbiBkZSBtZWlsbGV1cmVzIGZlYXR1cmVzLiBBIGxhIHBsYWNlLCBub3VzIG9wdGltaXNlcm9ucyBsZXMgaHlwZXJwYXJhbcOodHJlcyBldCBsZXMgZmVhdHVyZXMgc8OpbGVjdGlvbm7DqWVzLg0KDQojIyBPcHRpbWlzYXRpb24gcGFyIGVudHJvcGllIGNyb2lzw6llDQoNCk5vdXMgYWxsb25zIHV0aWxpc2VyIGwnb3B0aW1pc2F0aW9uIHBhciBlbnRyb3BpZSBjcm9pc8OpZSAoQ3Jvc3MtRW50cm9weSBPcHRpbWl6YXRpb24pLCBxdWkgZG9ubmUgZGVzIHLDqXN1bHRhdHMgcmVtYXJxdWFibGVzIGRhbnMgbGEgcXVhc2kgaW50w6lncmFsaXTDqSBkZXMgY2FzICjDoCBtb2lucyBxdWUgdG91dGVzIGxlcyBmZWF0dXJlcyBldCB0b3VzIGxlcyBoeXBlcnBhcmFtw6h0cmVzIHNvaWVudCBkw6lqw6AgbCd1biBkZXMgbWVpbGxldXJzIHBvc3NpYmxlcykuIEdyw6JjZSDDoCBjZXR0ZSBtw6l0aG9kZSwgbm91cyBwb3V2b25zIDoNCg0KLSBPcHRpbWlzZXIgbGVzIGh5cGVycGFyYW3DqHRyZXMgZGUgbm9zIGNob2l4LCBxdSdpbHMgc29pZW50IGNvbnRpbnVzIG91IGRpc2NyZXRzDQotIFPDqWxlY3Rpb25uZXIgZGVzIGZlYXR1cmVzIChkaXNjcsOpdGlzYXRpb24gYmluYWlyZSBwb3VyIGxhIHPDqWxlY3Rpb24pDQoNCkljaSwgbm91cyBhdm9ucyB0cm9pcyBoeXBlcnBhcmFtw6h0cmVzIGV0IDM2IGZlYXR1cmVzIMOgIHPDqWxlY3Rpb25uZXIgOg0KDQotIGFscGhhIDogcsOpZ3VsYXJpc2F0aW9uIEwxIHN1ciBsZXMgY29lZmZpY2llbnRzLCBxdSdvbiB2YSBjb25zdHJhaW5kcmUgZW50cmUgMCBldCA1DQotIGxhbWJkYSA6IHLDqWd1bGFyaXNhdGlvbiBMMiBzdXIgbGVzIGNvZWZmaWNpZW50cywgcXUnb24gdmEgY29uc3RyYWluZHJlIGVudHJlIDAgZXQgNQ0KLSBsYW1iZGFfYmlhcyA6IHLDqWd1bGFyaXNhdGlvbiBMMiBzdXIgbGUgYmlhaXMsIHF1J29uIHZhIGNvbnN0cmFpbmRyZSBlbnRyZSAwIGV0IDUNCi0gMzYgZmVhdHVyZXMgOiBvbiBzb3VoYWl0ZSByw6lkdWlyZSBkZSA3MiUgZW52aXJvbiBsZSBub21icmUgZGUgZmVhdHVyZXMgcG91ciBxdWUgbGUgbW9kw6hsZSBmaW5hbCBzb2l0IHBlcmZvcm1hbnQgZXQgc2ltcGxlIMOgIGNvbXByZW5kcmUgKGVudmlyb24gMTAgZmVhdHVyZXMpDQoNClBvdXIgbmUgcGFzIHF1ZSBsJ29wdGltaXNldXIgY29udmVyZ2UgdHJvcCByYXBpZGVtZW50LCBub3VzIGFsbG9ucyB1dGlsaXNlciBjZXMgcGFyYW3DqHRyZXMgZCdvcHRpbWlzYXRpb24gOg0KDQotIG9wdGltaXNhdGlvbiBkdSBib29zdGluZyA6IG5vdXMgYWxsb25zIHV0aWxpc2VyIGxhIHBlcnRlIGxvZ2FyaXRobWlxdWUgZXQgbm9uIHBhcyBsJ2luZXhhY3RpdHVkZSBwb3VyIGludGVycm9tcHJlIGxlIGJvb3N0aW5nLCBhZmluIGQnw6l2aXRlciB1biBlZmZldCBkZSBjaGFuY2UgZXQgZmFpcmUgY29udmVyZ2VyIHBsdXMgcmFwaWRlbWVudCBjaGFxdWUgbW9kw6hsZQ0KLSB2YWxldXIgw6Agb3B0aW1pc2VyIDogbWluaW1pc2F0aW9uIGRlIGxhIHBlcnRlIGxvZ2FyaXRobWlxdWUgZGUgY2xhc3NpZmljYXRpb24gKGxvZ2xvc3MpIGV0IG5vbiBwYXMgZGUgbCdpbmV4YWN0aXR1ZGUgZGUgY2xhc3NpZmljYXRpb24gYWZpbiBkJ8Opdml0ZXIgbCdvdmVyZml0dGluZyBpbnZvbG9udGFpcmUgb3UgbCdlZmZldCBkZSBjaGFuY2UgZHVlIMOgIGxhIGZhaWJsZSBxdWFudGl0w6kgZCdvYnNlcnZhdGlvbnMgKG1haXMgw6lnYWxlbWVudCBwb3VyIGZvdXJuaXIgdW5lIMOpY2hlbGxlIGNvbnRpbnVlIGV0IHBhcyBkaXNjcsOodGUgw6AgbCdvcHRpbWlzZXVyIHBvdXIgbGEgcGVydGUpIC0gcG91ciB0ZW50ZXIgZCdvcHRpbWlzZXIgbCdleGFjdGl0dWRlLCBvbiBtdWx0aXBsaWUgbCdpbmV4YWN0aXR1ZGUgcGFyIGxhIHBlcnRlIGxvZ2FyaXRobWlxdWUgcXVpIHNlcmEgcGFzc8OpZSBjb21tZSB2YWxldXIgw6Agb3B0aW1pc2VyIHBhciBlbnRyb3BpZSBjcm9pc8OpZQ0KLSDDqWNoYW50aWxsb25zIHBhciBpdMOpcmF0aW9ucyA6IDI1MCBtb2TDqGxlcyBwb3VyIHVuZSBwb3B1bGF0aW9uIHN0YWJsZSBldCBkaXZlcnNlDQotIGVsaXRlcyA6IDEwJSBkJ2VsaXRlcyBwb3VyIGF2b2lyIHVuZSBwb3B1bGF0aW9uIHBsdXTDtHQgc3RhYmxlIGV0IGRpdmVyc2UNCi0gbm9tYnJlIGQnaXTDqXJhdGlvbnMgOiAyMCBwb3VyIGxhaXNzZXIgw6AgbCdvcHRpbWlzZXVyIGxlIHRlbXBzIGRlIGNoZXJjaGVyIChtYWlzIHRyw6hzIHJhcGlkZW1lbnQpLCBhdmVjIGFycsOqdCBwcsOpbWF0dXLDqSBlbiBjYXMgZGUgc3RhZ25hdGlvbiBkZSBsJ29wdGltaXNhdGlvbiBwZW5kYW50IDUgaXTDqXJhdGlvbnMNCi0gb3B0aW1pc2F0aW9uIGRlcyB2YWxldXJzIGNvbnRpbnVlcyA6IGNvbnZlcmdlbmNlIGxvcnNxdWUgbGVzIGh5cGVycGFyYW3DqHRyZXMgb250IGNoYWN1biB1biDDqWNhcnQtdHlwZSBlbi1kZXNzb3VzIGRlIDAuMTAgZGFucyBsYSBwb3B1bGF0aW9uIMOpbGl0ZQ0KLSBvcHRpbWlzYXRpb24gZGVzIHZhbGV1cnMgZGlzY3LDqHRlcyA6IGNvbnZlcmdlbmNlIGxvcnNxdWUgbGEgc8OpbGVjdGlvbiBkZXMgZmVhdHVyZXMgZXN0IGlkZW50aXF1ZSBkYW5zIGxhIHBvcHVsYXRpb24gw6lsaXRlDQoNClRvdXRlcyBsZXMgdmFyaWFibGVzIG9wdGltaXPDqWVzIHNlcm9udCBsb2dnw6llcyBldCBsZXVycyDDqXZvbHV0aW9ucyBzZXJvbnQgdmlzaWJsZXMgZW4gdGVtcHMgcsOpZWwgdmlhIGRpdmVycyBsb2dpY2llbHMgKGV4ZW1wbGUgOiBCYXJlVGFpbCksIGF2ZWMgbGUgdGFnICIqKioiIGxvcnNxdWUgbCdvcHRpbWlzYXRpb24gZG9ubmUgdW4gbm91dmVhdSBtaW5pbXVtIGxvY2FsLg0KDQpVbmUgcmVjaGVyY2hlIGV4aGF1c3RpdmUgbmUgZm9uY3Rpb25uZXJhIHBhcyBtw6ptZSBhdmVjIHVuIHBldGl0IHN1YnNldCBkZSBmZWF0dXJlcyByZWNoZXJjaMOpZXMgOiBub3VzIHNvbW1lcyBlbiBwcsOpc2VuY2UgZCdoeXBlcnBhcmFtw6h0cmVzIGNvbnRpbnVzIGV0IG5vbiBkaXNjcmV0cy4NCg0KQSBsYSBmaW4sIG5vdXMgYXZvbnMgMTMgZmVhdHVyZXMgYXZlYyB1bmUgbWVpbGxldXJlIHBlcmZvcm1hbmNlIHF1ZSBjZWxsZSBkdSBtb2TDqGxlIGluaXRpYWxlIChqdXNxdSfDoCA3MiUgZCdleGFjdGl0dWRlIGNvbnRyZSA2NSUgaW5pdGlhbGVtZW50KS4gTm90cmUgbW9kw6hsZSBuw6ljZXNzaXRlIGRvbmMgcGx1cyBkZSBmZWF0dXJlcyBxdSdvbiBzb3VoYWl0YWl0ICgzNiUgZGVzIGZlYXR1cmVzIGF1IGxpZXUgZGUgMjglKSwgbWFpcyBsZSBnYWluIGVuIHBlcmZvcm1hbmNlIGVzdCBtYWpldXIgKCs3JSkuIE9uIHBldXQgYW5hbHlzZXIgbGUgbG9nIHBvdXIgZXh0cmFpcmUgbGEgbWVpbGxldXJlIGNvbWJpbmFpc29uIHF1aSBub3VzIHBlcm1ldCBkZSBjb25zZXJ2ZXIgdW5pcXVlbWVudCBkZXMgMTAgZmVhdHVyZXMsIG1haXMgb24gbmUgbGUgZmVyYSBwYXMgcGFyIG1hbnF1ZSBkZSB0ZW1wcyBpY2kuDQoNCmBgYHtyIE9wdGltaXNhdGlvbiwgY2FjaGU9VFJVRX0NCiMgQ29tcHRldXIgZGUgdGVtcHMNCkN1cnJlbnRUaW1lIDwtIHRpbWVyKCkgIyBPcHRpbWlzYXRpb24gcGFyIGVudHJvcGllIGNyb2lzw6llDQoNCkNFX0ZlYXR1cmVzIDwtIGZ1bmN0aW9uKHgsIHksIHRyYWluLCB0ZXN0KSB7DQogIA0KICAjIFByw6ktaW5pdGlhbGlzYXRpb24gZGUgY2VydGFpbmVzIHZhcmlhYmxlcw0KICB0b19rZWVwIDwtIGFzLmJvb2xlYW4oeSkNCiAgaXRlcnMgPDwtIGl0ZXJzICsgMQ0KICANCiAgIyBBdSBtb2lucyB1bmUgZmVhdHVyZSA/DQogIGlmIChzdW0odG9fa2VlcCkgPiAwKSB7DQogICAgDQogICAgZXJyb3IgPC0gbnVtZXJpYygzKQ0KICAgIGxsb3NzIDwtIG51bWVyaWMoMykNCiAgICANCiAgICBmb3IgKGkgaW4gMTA6MTIpIHsNCiAgICAgIA0KICAgICAgdGVtcF9tb2RlbCA8LSB4Z2IudHJhaW4oZGF0YSA9IHhnYi5ETWF0cml4KGRhdGEgPSBhcy5tYXRyaXgodHJhaW5bW2ldXVssIHdoaWNoKHRvX2tlZXApXSksIGxhYmVsID0gdHJhaW5bW2ldXVtbIkxhYmVsIl1dKSwgIyBEb25uw6llcyBkJ2VudHJhaW5lbWVudA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgd2F0Y2hsaXN0ID0gbGlzdCh0ZXN0ID0geGdiLkRNYXRyaXgoZGF0YSA9IGFzLm1hdHJpeCh0ZXN0W1tpXV1bLCB3aGljaCh0b19rZWVwKV0pLCBsYWJlbCA9IHRlc3RbW2ldXVtbIkxhYmVsIl1dKSksICMgRG9ubsOpZXMgZGUgdmFsaWRhdGlvbg0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbnVtX2NsYXNzID0gNiwgIyA2IGNsYXNzZSBkZSBjbGFzc2lmaWNhdGlvbg0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbnRocmVhZCA9IDEsICMgMSB0aHJlYWQgcG91ciBsYSByZXByb2R1Y3Rpb24gZGVzIHLDqXN1bHRhdHMNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5yb3VuZHMgPSAxMDAwLCAjIDEwMDAgaXTDqXJhdGlvbnMsIG/DuSBhcnLDqnQgcHLDqW1hdHVyw6kNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFscGhhID0geFsxXSwgIyBSw6lndWxhcmlzYXRpb24gTDEgKExhc3NvKQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGFtYmRhID0geFsyXSwgIyBSw6lndWxhcmlzYXRpb24gTDIgKFJpZGdlKQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGFtYmRhX2JpYXMgPSB4WzNdLCAjIFLDqWd1bGFyaXNhdGlvbiBMMiBkdSBiaWFpcyAoUmlkZ2UpDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBldGEgPSAwLjEwLCAjIFNocmlua2FnZSBwb3VyIGxlIGJvb3N0aW5nDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBib29zdGVyID0gImdibGluZWFyIiwgIyBUeXBlIGQnZW50cmFpbmVtZW50IDogbGluw6lhaXJlIG91IG5vbi1saW7DqWFpcmUNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG9iamVjdGl2ZSA9ICJtdWx0aTpzb2Z0cHJvYiIsICMgR3JhZGllbnQvSGVzc2lhbiBwb3VyIGwnb3B0aW1pc2F0aW9uIHBhciBHcmFkaWVudCBEZXNjZW50DQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBldmFsX21ldHJpYyA9ICJtbG9nbG9zcyIsICMgUGVydGUgbG9naWFydGhtaXF1ZSBkZSBsYSBjbGFzc2lmaWNhdGlvbiAoY2V0dGUgbcOpdHJpcXVlIGVzdCBvcHRpbWlzw6llIHBhciB4Z2Jvb3N0KQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZXZhbF9tZXRyaWMgPSAibWVycm9yIiwgIyBJbmV4YWN0aXR1ZGUgZGUgbGEgY2xhc3NpZmljYXRpb24NCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1heGltaXplID0gRkFMU0UsICMgTWluaW1pc2F0aW9uIGRlIGwnZXJyZXVyDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBlYXJseV9zdG9wcGluZ19yb3VuZHMgPSA1MCwgIyBBcnLDqnQgYXByw6hzIDUwIGl0w6lyYXRpb25zIHNhbnMgYW3DqWxpb3JhdGlvbiBkZSBsYSBtw6l0cmlxdWUNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHZlcmJvc2UgPSBGQUxTRSwgIyBTYW5zIHByaW50IGRlcyBpdMOpcmF0aW9ucw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY2FsbGJhY2tzID0gbGlzdChjYi5ldmFsdWF0aW9uLmxvZygpKSkgIyBMb2dnaW5nIGRlcyBkb25uw6llcyBkJ2VudHJhaW5lbWVudCBwb3VyIHBvdXZvaXIgcsOpY3Vww6lyZXIgbGVzIG3DqXRyaXF1ZXMpDQogICAgICBlcnJvclsxMyAtIGldIDwtIHRlbXBfbW9kZWwkZXZhbHVhdGlvbl9sb2ckdGVzdF9tZXJyb3JbdGVtcF9tb2RlbCRiZXN0X2l0ZXJhdGlvbl0gIyBFbnJlZ2lzdHJlbWVudCBkdSBtZWlsbGV1ciBzY29yZSAoaW5leGFjdGl0dWRlKQ0KICAgICAgbGxvc3NbMTMgLSBpXSA8LSB0ZW1wX21vZGVsJGV2YWx1YXRpb25fbG9nJHRlc3RfbWxvZ2xvc3NbdGVtcF9tb2RlbCRiZXN0X2l0ZXJhdGlvbl0gIyBFbnJlZ2lzdHJlbWVudCBkdSBtZWlsbGV1ciBzY29yZSAocGVydGUgbG9nYXJpdGhtaXF1ZSkNCiAgICAgIA0KICAgIH0NCiAgICANCiAgICAjIEVucmVnaXN0cmVtZW50IGRlIGwnZXJyZXVyIGV0IGxvZ2dpbmcgZGFucyBsJ2Vudmlyb25uZW1udCBnbG9iYWwNCiAgICBlcnJvcl9saXN0W2l0ZXJzLCAyOjRdIDw8LSBlcnJvciAjIEluZXhhY3RpdHVkZQ0KICAgIGVycm9yIDwtIG1lYW4oZXJyb3IpICMgSW5leGFjdGl0dWRlIG1veWVubmUNCiAgICBlcnJvcl9saXN0W2l0ZXJzLCA1XSA8PC0gZXJyb3IgIyBJbmV4YWN0aXR1ZGUgbW95ZW5uZQ0KICAgIGVycm9yX2xpc3RbaXRlcnMsIDY6OF0gPDwtIGxsb3NzICMgUGVydGUgbG9nYXJpdGhtaXF1ZQ0KICAgIGxsb3NzIDwtIG1lYW4obGxvc3MpICMgUGVydGUgbG9nYXJpdGhtaXF1ZSBtb3llbm5lDQogICAgZXJyb3JfbGlzdFtpdGVycywgOV0gPDwtIGxsb3NzICMgUGVydGUgbG9nYXJpdGhtaXF1ZSBtb3llbm5lDQogICAgc2NvcmUgPC0gZXJyb3IgKiBsbG9zcyAjIFNjb3JlIGRlIHBlcnRlDQogICAgZXJyb3JfbGlzdFtpdGVycywgMTBdIDw8LSBzY29yZSAjIFNjb3JlIGRlIHBlcnRlDQogICAgZXJyb3JfbGlzdFtpdGVycywgMTFdIDw8LSBzdW0odG9fa2VlcCkgIyBDb21wdGUgZGUgZmVhdHVyZXMNCiAgICBlcnJvcl9saXN0W2l0ZXJzLCAxMjoxNF0gPDwtIGFzLm51bWVyaWMoeCkgIyBIeXBlcnBhcmFtw6h0cmVzDQogICAgZXJyb3JfbGlzdFtpdGVycywgMTU6NTBdIDw8LSBhcy5udW1lcmljKHRvX2tlZXApICMgRmVhdHVyZXMgdXRpbGlzw6llcw0KICAgIGVycm9yX2xpc3RbaXRlcnMsIDUxXSA8PC0gMQ0KICAgIA0KICAgICMgTGUgcsOpc3VsdGF0IGVzdC1pbCBtZWlsbGV1ciA/IChwb3VyIGxlIGxvZ2dpbmcgZW4gdGVtcHMgcsOpZWwpDQogICAgaWYgKGVycm9yIDwgYmVzdF9lcnJvcikgew0KICAgICAgYmVzdF9lcnJvciA8PC0gZXJyb3INCiAgICAgIHN0YXIgPC0gIigqKiogLSAiDQogICAgfSBlbHNlIHsNCiAgICAgIHN0YXIgPC0gIiggICAgLSAiDQogICAgfQ0KICAgIGlmIChsbG9zcyA8IGJlc3RfbGxvc3MpIHsNCiAgICAgIGJlc3RfbGxvc3MgPDwtIGxsb3NzDQogICAgICBzdGFyIDwtIHBhc3RlMChzdGFyLCAiKioqIC0gIikNCiAgICB9IGVsc2Ugew0KICAgICAgc3RhciA8LSBwYXN0ZTAoc3RhciwgIiAgICAtICIpDQogICAgfQ0KICAgIGlmIChzY29yZSA8IGJlc3Rfc2NvcmUpIHsNCiAgICAgIGJlc3Rfc2NvcmUgPDwtIHNjb3JlDQogICAgICBzdGFyIDwtIHBhc3RlMChzdGFyLCAiKioqKSAiKQ0KICAgIH0gZWxzZSB7DQogICAgICBzdGFyIDwtIHBhc3RlMChzdGFyLCAiICAgKSAiKQ0KICAgIH0NCiAgICANCiAgICAjIExvZ2dpbmcgZW4gdGVtcHMgcsOpZWwNCiAgICBjYXQoc3RhciwgIlsiLCBmb3JtYXQoU3lzLnRpbWUoKSwgIiVYIiksICJdIFBhc3MgIiwgc3ByaW50ZigiJTA1ZCIsIGl0ZXJzKSwgIjogRXJyb3I9Iiwgc3ByaW50ZigiJS4wNWYiLCBlcnJvciksICIgLSBMb3NzPSIsIHNwcmludGYoIiUuMDdmIiwgbGxvc3MpLCAiIC0gU2NvcmU9Iiwgc3ByaW50ZigiJS4wN2YiLCBzY29yZSksICIgLSBmZWF0cz0iLCBzcHJpbnRmKCIlMDRkIiwgc3VtKHRvX2tlZXApKSwgIiAtIGFscGhhPSIsIHNwcmludGYoIiUwNy4wNWYiLCB4WzFdKSwgIiwgbGFtYmRhPSIsIHNwcmludGYoIiUwNy4wNWYiLCB4WzJdKSwgIiwgbGFtYmRhX2JpYXM9Iiwgc3ByaW50ZigiJTA3LjA1ZiIsIHhbM10pLCAiXG4iLCBzZXAgPSAiIiwgZmlsZSA9ICJvcHRpbS9sb2cudHh0IiwgYXBwZW5kID0gVFJVRSkNCiAgICByZXR1cm4oc2NvcmUpDQogICAgDQogIH0gZWxzZSB7DQogICAgDQogICAgIyBMb2dnaW5nIGVuIHRlbXBzIHLDqWVsDQogICAgY2F0KCIoICAgIC0gICAgIC0gICAgKSBbIiwgZm9ybWF0KFN5cy50aW1lKCksICIlWCIpLCAiXSBQYXNzICIsIHNwcmludGYoIiUwNWQiLCBpdGVycyksICI6IGZhaWxlZFxuIiwgc2VwID0gIiIsIGZpbGUgPSAib3B0aW0vbG9nLnR4dCIsIGFwcGVuZCA9IFRSVUUpDQogICAgcmV0dXJuKDkuOTk5OSkNCiAgICANCiAgfQ0KICANCn0NCg0KIyBPw7kgc2F1dmVnYXJkZXIgbGVzIGZpY2hpZXJzID8NCmZpbGVfdGFnIDwtICI0X2RhdGEvIg0KDQojIENyw6lhdGlvbiBkZXMgZG9ubsOpZXMgZCdlbnRyYWluZW1lbnQgZXQgZGUgdmFsaWRhdGlvbg0KZm9yIChpIGluIDE6MTIpIHsNCiAgDQogICMgQ3LDqWF0aW9uIGRlcyBkb25uw6llcyBkJ2VudHJhaW5lbWVudCBldCBkZSB2YWxpZGF0aW9uDQogIHRyYWluaW5nX2RhdGFbW2ldXVtbIkxhYmVsIl1dIDwtIGdyb3VwX3BhdGgkcGF0aF9JRFtmb2xkc190cmFpbltbaV1dXSAtIDENCiAgdGVzdGluZ19kYXRhW1tpXV1bWyJMYWJlbCJdXSA8LSBncm91cF9wYXRoJHBhdGhfSURbZm9sZHNfdGVzdFtbaV1dXSAtIDENCiAgDQp9DQoNCiMgUGFyYW3DqHRyZXMgZGUgbCdvcHRpbWlzZXVyDQpjb250X29wdCA8LSBsaXN0KG1lYW4gPSBjKDEsIDEsIDEpLCAjIETDqWJ1dGUgYXZlYyBlbiBtb3llbm5lLCBBbHBoYT0xLCBMYW1iZGE9MSwgTGFtYmRhX2JpYXM9MQ0KICAgICAgICAgICAgICAgICBzZCA9IGMoMSwgMSwgMSksICMgRMOpYnV0ZSBhdmVjIGVuIMOpY2FydC10eXBlLCBBbHBoYT0xLCBMYW1iZGE9MSwgTGFtYmRhX2JpYXM9MQ0KICAgICAgICAgICAgICAgICBjb25NYXQgPSByYmluZChkaWFnKDMpLCAtZGlhZygzKSksICMgT3B0aW1pc2F0aW9uIGxpbsOpYWlyZSBjb25kaXRpb25uw6llIHBhciBsYSBtYXRyaWNlIGR1IHNpbXBsZXhlDQogICAgICAgICAgICAgICAgIGNvblZlYyA9IGMoNSwgNSwgNSwgMCwgMCwgMCksICMgMDw9YWxwaGE8PTUsIDA8PUxhbWJkYTw9NSwgMDw9TGFtYmRhX2JpYXM8PTUNCiAgICAgICAgICAgICAgICAgc2RUaHIgPSAwLjEpICMgT24gc3VwcG9zZSBsZXMgaHlwZXJwYXJhbcOodHJlcyBjb252ZXJnw6lzIGxvcnNxdWUgdG91cyBsZXMgw6ljYXJ0LXR5cGVzIHNvbnQgZW4tZGVzc291cyBkZSAwLjENCnAwIDwtIGxpc3QoKSAjIFByw6ktaW5pdGFpc2xpYXRpb24gZGUgbGEgbGlzdGUgcG91ciBsZXMgdmFyaWFibGVzIGRpc2Nyw6h0ZXMNCmZvciAoaSBpbiAxOjM2KSB7cDAgPC0gYyhwMCwgbGlzdChjKDAuNzIsIDAuMjgpKSl9ICMgT24gc291aGFpdGUgNTAlIGRlcyBmZWF0dXJlcyDDoCBsYSBmaW4gZW4gbW95ZW5uZSwgw6AgbW9pbnMgcXVlIGNlcnRhaW5lcyB2YXJpYWJsZXMgb250IHVuZSBpbXBvcnRhbmNlIHRlbGxlIHF1J2VsbGVzIG5lIHBldXZlbnQgw6p0cmUgb21pc2VzIGV0IHNlcm9udCBmb3Jjw6ltZW50IHPDqWxlY3Rpb25uw6llcw0KZGlzY19vcHQgPC0gbGlzdChwcm9icyA9IHAwLA0KICAgICAgICAgICAgICAgICBzbW9vdGhQcm9iID0gMS4wMCwgIyBPbiB2YSB0ZW50ZXIgZGUgY29udmVyZ2VyIHJhcGlkZW1lbnQgaWNpIHBvdXIgbGEgcsOpYWxpc3Rpb24gZCd1biBwcm9vZiBvZiBjb25jZXB0LCBtYWlzIHNpbm9uIGF2ZWMgcGx1cyBkZSB0ZW1wcyBvbiBwb3VycmEgcsOpYWxpc2VyIHVuIHNocmlua2FnZSBkZSA1JSBkZSBsYSBwcm9iYWJpbGl0w6kgw6lsaXRlIMOgIGNoYXF1ZSBpdMOpcmF0aW9uIChzbW9vdGhQcm9iID0gMC45NSkNCiAgICAgICAgICAgICAgICAgcHJvYlRociA9IDAuMDAwMSkgIyBPbiBzdXBwb3NlIGxhIHPDqWxlY3Rpb24gZGUgZmVhdHVyZXMgY29udmVyZ8OpIGxvcnNxdWUgdG91dGVzIGxlcyBwcm9iYWJpbGl0w6lzIHNvbnQgZW4tZGVzc291cyBkZSAwLjAwMDENCm5fZmFtaWx5IDwtIDI1MCAjIExlIG5vbWJyZSBkJ2VzdGltYXRpb25zIHBhciBpdMOpcmF0aW9uIGRlIGwnb3B0aW1pc2V1cg0KZWxpdGUgPC0gMC4xICMgTGUgbm9tYnJlIGQnw6lsaXRlcyBwYXIgaXTDqXJhdGlvbiBxdWkgZGljdGVudCBsYSBsb2kgZGFucyBsJ2VudHJvcGllIGNyb2lzw6llDQppdGVyYXRpb25zIDwtIDIxICMgTGUgbm9tYnJlIGQnaXTDqXJhdGlvbnMgZCdvcHRpbWlzYXRpb24gKHBsdXMgdW4gcG91ciBsJ2l0w6lyYXRpb24gZCdpbml0aWFsaXNhdGlvbikNCmVhcmx5X3N0b3AgPC0gNSAjIEFycsOqdCBwcsOpbWF0dXLDqSBsb3JzcXVlIGxhIGZvbmN0aW9uIGRlIHBlcnRlIChpY2kgbCdpbmV4YWN0aXR1ZGUgZGUgbGEgY2xhc3NpZmljYXRpb24pIG5lIGRpbWludWUgcGFzIGFwcsOocyBYIGl0w6lyYXRpb25zDQoNCiMgUHLDqS1pbml0aWFsaXNhdGlvbiBkZSBsYSB2YXJpYWJsZSBkZSBsb2dnaW5nDQppdGVycyA8LSAwICMgU3VpdmkgZGUgbCdpdMOpcmF0aW9uDQpiZXN0X2Vycm9yIDwtIDEgIyBTdWl2aSBkZSBsYSBwZXJ0ZSAoaW5leGFjdGl0dWRlKSwgaW5pdGlhbGlzw6kgw6AgdW5lIHRyw6hzIG1hdXZhaXNlIHZhbGV1ciBwb3NzaWJsZQ0KYmVzdF9sbG9zcyA8LSA5Ljk5OTkgIyBTdWl2aSBkZSBsYSBwZXJ0ZSAobG9nYXJpdGhtaXF1ZSksIGluaXRpYWxpc8OpIMOgIHVuZSB0csOocyBtYXV2YWlzZSB2YWxldXIgcG9zc2libGUNCmJlc3Rfc2NvcmUgPC0gOS45OTk5ICMgU3VpdmkgZGUgbGEgcGVydGUgKGluZXhhY3RpdHVkZSAqIGxvZ2FyaXRobWlxdWUpLCBpbml0aWFsaXPDqSDDoCB1bmUgdHLDqHMgbWF1dmFpc2UgdmFsZXVyIHBvc3NpYmxlDQplcnJvcl9saXN0IDwtIGRhdGEuZnJhbWUoSXRlcmF0aW9uID0gMToobl9mYW1pbHkgKiBpdGVyYXRpb25zKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICBFcnJvcl8xID0gbnVtZXJpYyhuX2ZhbWlseSAqIGl0ZXJhdGlvbnMpLA0KICAgICAgICAgICAgICAgICAgICAgICAgIEVycm9yXzIgPSBudW1lcmljKG5fZmFtaWx5ICogaXRlcmF0aW9ucyksDQogICAgICAgICAgICAgICAgICAgICAgICAgRXJyb3JfMyA9IG51bWVyaWMobl9mYW1pbHkgKiBpdGVyYXRpb25zKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICBFcnJvcl9NZWFuID0gbnVtZXJpYyhuX2ZhbWlseSAqIGl0ZXJhdGlvbnMpLA0KICAgICAgICAgICAgICAgICAgICAgICAgIExvc3NfMSA9IG51bWVyaWMobl9mYW1pbHkgKiBpdGVyYXRpb25zKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICBMb3NzXzIgPSBudW1lcmljKG5fZmFtaWx5ICogaXRlcmF0aW9ucyksDQogICAgICAgICAgICAgICAgICAgICAgICAgTG9zc18zID0gbnVtZXJpYyhuX2ZhbWlseSAqIGl0ZXJhdGlvbnMpLA0KICAgICAgICAgICAgICAgICAgICAgICAgIExvc3NfTWVhbiA9IG51bWVyaWMobl9mYW1pbHkgKiBpdGVyYXRpb25zKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICBTY29yZSA9IG51bWVyaWMobl9mYW1pbHkgKiBpdGVyYXRpb25zKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICBGZWF0dXJlc19uID0gbnVtZXJpYyhuX2ZhbWlseSAqIGl0ZXJhdGlvbnMpLA0KICAgICAgICAgICAgICAgICAgICAgICAgIEFscGhhID0gbnVtZXJpYyhuX2ZhbWlseSAqIGl0ZXJhdGlvbnMpLA0KICAgICAgICAgICAgICAgICAgICAgICAgIExhbWJkYSA9IG51bWVyaWMobl9mYW1pbHkgKiBpdGVyYXRpb25zKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICBMYW1iZGFfYmlhcyA9IG51bWVyaWMobl9mYW1pbHkgKiBpdGVyYXRpb25zKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICBtYXRyaXgocmVwKDAsIG5fZmFtaWx5ICogaXRlcmF0aW9ucyAqIDM2KSwgbmNvbCA9IDM2KSwNCiAgICAgICAgICAgICAgICAgICAgICAgICBMb2dnaW5nID0gcmVwKDAsIG5fZmFtaWx5ICogaXRlcmF0aW9ucykpDQpjb2xuYW1lcyhlcnJvcl9saXN0KVsxNTo1MF0gPC0gYyhwYXN0ZTAocmVwKGMocGFzdGUwKCJDb2VmIiwgMTo0KSwgcGFzdGUwKCJSw6lzaSIsIDE6NCkpLCA0KSwgcGFzdGUwKCJfIiwgaW52ZXJzZS5ybGUobGlzdChsZW5ndGhzID0gcmVwKDgsIDQpLCB2YWx1ZXMgPSAxOjQpKSkpLCBwYXN0ZTAoIlBvc0luaXRpYWxlXyIsIDE6NCkpDQoNCnNldC5zZWVkKDApICMgRml4YXRpb24gZHUgc2VlZCBhbMOpYXRvaXJlIHBvdXIgZGVzIHLDqXN1bHRhdHMgcXVpIHB1aXNzZW50IMOqdHJlIHJlcHJvZHVpdHMNCg0KIyBPcHRpbWlzYXRpb24gcGFyIGVudHJvcGllIGNyb2lzw6llDQpiZXN0X3dlaWdodHMgPC0gQ0VvcHRpbShDRV9GZWF0dXJlcywNCiAgICAgICAgICAgICAgICAgICAgICAgIGYuYXJnID0gbGlzdCh0cmFpbiA9IHRyYWluaW5nX2RhdGEsICMgRG9ubsOpZXMgZCdlbnRyYWluZW1lbnQNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0ZXN0ID0gdGVzdGluZ19kYXRhKSwgIyBEb25uw6llcyBkZSB2YWxpZGF0aW9uDQogICAgICAgICAgICAgICAgICAgICAgICBtYXhpbWl6ZSA9IEZBTFNFLCAjIE1pbmltaXNhdGlvbiBkdSBwcm9ibMOobWUNCiAgICAgICAgICAgICAgICAgICAgICAgIGNvbnRpbnVvdXMgPSBjb250X29wdCwNCiAgICAgICAgICAgICAgICAgICAgICAgIGRpc2NyZXRlID0gZGlzY19vcHQsDQogICAgICAgICAgICAgICAgICAgICAgICBOID0gbl9mYW1pbHksDQogICAgICAgICAgICAgICAgICAgICAgICByaG8gPSBlbGl0ZSwNCiAgICAgICAgICAgICAgICAgICAgICAgIHZlcmJvc2UgPSBUUlVFLA0KICAgICAgICAgICAgICAgICAgICAgICAgaXRlclRociA9IGl0ZXJhdGlvbnMgLSAxLA0KICAgICAgICAgICAgICAgICAgICAgICAgbm9JbXByb3ZlVGhyID0gZWFybHlfc3RvcCkNCg0KIyBFbnJlZ2lzdHJlbWVudCBkdSBsb2cgZMOpdGFpbGzDqQ0KZndyaXRlKGVycm9yX2xpc3QsICJvcHRpbS9lcnJvcl9yYXcuY3N2IikNCg0KIyBFbnJlZ2lzdHJlbWVudCBkdSBsb2cgZMOpdGFpbGzDqSBldCBuZXR0b3nDqSBkZXMgw6lsw6ltZW50cyBpbnV0aWxlcw0KZXJyb3JfbGlzdCA8LSBlcnJvcl9saXN0W2Vycm9yX2xpc3QkTG9nZ2luZyA9PSAxLCBdDQpmd3JpdGUoZXJyb3JfbGlzdCwgIm9wdGltL2Vycm9yX2NsZWFuLmNzdiIpDQoNCiMgRW5yZWdpc3RyZW1lbnQgZGUgbGEgdmFyaWJsZSBjb250ZW5hbnQgbCdvcHRpbWlzYXRpb24NCnNhdmVSRFMoYmVzdF93ZWlnaHRzLCAib3B0aW0vb3B0aW1pemVkLnJkcyIpDQoNCiMgQWZmaWNoYWdlIGRlcyByw6lzdWx0YXRzDQp4IDwtIGJlc3Rfd2VpZ2h0cyRvcHRpbWl6ZXIkY29udGludW91cyAjIFLDqWN1cMOpcmF0aW9uIGRlcyBoeXBlcnBhcmFtw6h0cmVzDQp5IDwtIGJlc3Rfd2VpZ2h0cyRvcHRpbWl6ZXIkZGlzY3JldGUgIyBSw6ljdXDDqXJhdGlvbiBkZXMgZmVhdHVyZXMgc8OpbGVjdGlvbm7DqWVzDQpjYXQoIiAgXG5MJ29wdGltaXNldXIgYSB0cm91dsOpIDogIFxuICAtIE1laWxsZXVyZSBJbmV4YWN0aXR1ZGUgPSAiLCBiZXN0X2Vycm9yLCAiICBcbiAgLSBNZWlsbGV1cmUgUGVydGUgTG9nYXJpdGhtaXF1ZSA9ICIsIGJlc3RfbGxvc3MsICJcbiAgLSBhbHBoYSA9ICIsIHhbMV0sICIgIFxuICAtIGxhbWJkYSA9ICIsIHhbMl0sICIgIFxuIC0gbGFtYmRhX2JpYXMgPSAiLCB4WzNdLCAiICBcbiAgLSBmZWF0dXJlcyA9ICIsIHN1bShhcy5ib29sZWFuKHkpKSwgIiAoYmluYXJ5ID0gIiwgcGFzdGUoeSwgY29sbGFwc2UgPSAiIiksICIpICBcbiAgXG5GZWF0dXJlcyB1dGlsaXPDqWVzIDogIFxuIiwgc2VwID0gIiIpDQpkcHV0KGMocGFzdGUwKHJlcChjKHBhc3RlMCgiQ29lZiIsIDE6NCksIHBhc3RlMCgiUsOpc2kiLCAxOjQpKSwgNCksIHBhc3RlMCgiXyIsIGludmVyc2UucmxlKGxpc3QobGVuZ3RocyA9IHJlcCg4LCA0KSwgdmFsdWVzID0gMTo0KSkpKSwgcGFzdGUwKCJQb3NJbml0aWFsZV8iLCAxOjQpKVthcy5ib29sZWFuKHkpXSkgIyBBZmZpY2hhZ2UgZGVzIG5vbXMgZGVzIGZlYXR1cmVzDQoNCiMgVGVtcHMgbsOpY2Vzc2FpcmUNCnRpbWluZyhDdXJyZW50VGltZSwgIk9wdGltaXNhdGlvbiBwYXIgZW50cm9waWUgY3JvaXPDqWUiKQ0KYGBgDQoNCiMjIFZpc3VhbGlzYXRpb24gZGUgbCfDqXZvbHV0aW9uIGRlIGwnb3B0aW1pc2F0aW9uDQoNCk9uIHBldXQgdmlzdWFsaXNlciBsJ8Opdm9sdXRpb24gZGUgbCdvcHRpbWlzYXRpb24gw6AgcGFydGlyIGRlIGdyYXBoaXF1ZXMuIE9uIHJlbWFycXVlIHVuZSBjb252ZXJnZW5jZSBwbHV0w7R0IHJhcGlkZSBkZXMgcGVydGVzIChpbmV4YWN0aXR1ZGUsIGxvZ2xvc3MpLg0KDQpPbiByZW1hcnF1ZXJhIGwnaW52ZXJzaW9uIGVudHJlIGR1IG5pdmVhdSBkZXMgY291cmJlcyBkZXMgc2FsbGVzIDEgZXQgMiBsb3JzcXUnb24gb3Bwb3NlIHBlcnRlIGxvZ2FyaXRobWlxdWUgZXQgaW5leGFjdGl0dWRlLiBFbiBlZmZldCwgaWwgbidleGlzdGUgYXVjdW5lIGNvcnLDqWxhdGlvbiBkaXJlY3RlIGVudHJlIGxhIHBlcnRlIGxvZ2FyaXRobWlxdWUgZXQgbCdpbmV4YWN0aXR1ZGUuDQoNCmBgYHtyIFZpc09wdGltaXNhdGlvbn0NCmdncGxvdGx5KGdncGxvdChkYXRhID0gZXJyb3JfbGlzdFssIGMoIkl0ZXJhdGlvbiIsICJFcnJvcl9NZWFuIildLCBhZXNfc3RyaW5nKHggPSAiSXRlcmF0aW9uIiwgeSA9ICJFcnJvcl9NZWFuIikpICsgZ2VvbV9saW5lKCkgKyB0aGVtZV9idygpICsgbGFicyh0aXRsZSA9ICJFdm9sdXRpb24gZGUgbCdpbmV4YWN0aXR1ZGUgcGFyIHJhcHBvcnQgYXUgbm9tYnJlIGQnaXTDqXJhdGlvbnMgZGUgbW9kw6lsaXNhdGlvbnMgZW4gbW95ZW5uZSIpLCB3aWR0aCA9IDk2MCwgaGVpZ2h0ID0gNzIwKQ0KZ2dwbG90bHkoZ2dwbG90KGRhdGEgPSBkYXRhLmZyYW1lKEl0ZXJhdGlvbiA9IHJlcCgxOm5yb3coZXJyb3JfbGlzdCksIDMpLCBFcnJvciA9IGMoZXJyb3JfbGlzdFtbIkVycm9yXzEiXV0sIGVycm9yX2xpc3RbWyJFcnJvcl8yIl1dLCBlcnJvcl9saXN0W1siRXJyb3JfMyJdXSksIFNhbGxlID0gYXMuZmFjdG9yKGludmVyc2UucmxlKGxpc3QobGVuZ3RocyA9IHJlcChucm93KGVycm9yX2xpc3QpLCAzKSwgdmFsdWVzID0gMTozKSkpKSwgYWVzX3N0cmluZyh4ID0gIkl0ZXJhdGlvbiIsIHkgPSAiRXJyb3IiLCBjb2xvciA9ICJTYWxsZSIpKSArIGdlb21fbGluZSgpICsgdGhlbWVfYncoKSArIGxhYnModGl0bGUgPSAiRXZvbHV0aW9uIGRlIGwnaW5leGFjdGl0dWRlIHBhciByYXBwb3J0IGF1IG5vbWJyZSBkJ2l0w6lyYXRpb25zIGRlIG1vZMOpbGlzYXRpb24gcGFyIHNhbGxlIiksIHdpZHRoID0gOTYwLCBoZWlnaHQgPSA3MjApDQpnZ3Bsb3RseShnZ3Bsb3QoZGF0YSA9IGVycm9yX2xpc3RbLCBjKCJJdGVyYXRpb24iLCAiTG9zc19NZWFuIildLCBhZXNfc3RyaW5nKHggPSAiSXRlcmF0aW9uIiwgeSA9ICJMb3NzX01lYW4iKSkgKyBnZW9tX2xpbmUoKSArIHRoZW1lX2J3KCkgKyBsYWJzKHRpdGxlID0gIkV2b2x1dGlvbiBkdSBsb2dsb3NzIHBhciByYXBwb3J0IGF1IG5vbWJyZSBkJ2l0w6lyYXRpb25zIGRlIG1vZMOpbGlzYXRpb25zIGVuIG1veWVubmUiKSwgd2lkdGggPSA5NjAsIGhlaWdodCA9IDcyMCkNCmdncGxvdGx5KGdncGxvdChkYXRhID0gZGF0YS5mcmFtZShJdGVyYXRpb24gPSByZXAoMTpucm93KGVycm9yX2xpc3QpLCAzKSwgTG9zcyA9IGMoZXJyb3JfbGlzdFtbIkxvc3NfMSJdXSwgZXJyb3JfbGlzdFtbIkxvc3NfMiJdXSwgZXJyb3JfbGlzdFtbIkxvc3NfMyJdXSksIFNhbGxlID0gYXMuZmFjdG9yKGludmVyc2UucmxlKGxpc3QobGVuZ3RocyA9IHJlcChucm93KGVycm9yX2xpc3QpLCAzKSwgdmFsdWVzID0gMTozKSkpKSwgYWVzX3N0cmluZyh4ID0gIkl0ZXJhdGlvbiIsIHkgPSAiTG9zcyIsIGNvbG9yID0gIlNhbGxlIikpICsgZ2VvbV9saW5lKCkgKyB0aGVtZV9idygpICsgbGFicyh0aXRsZSA9ICJFdm9sdXRpb24gZHUgbG9nbG9zcyBwYXIgcmFwcG9ydCBhdSBub21icmUgZCdpdMOpcmF0aW9ucyBkZSBtb2TDqWxpc2F0aW9uIHBhciBzYWxsZSIpLCB3aWR0aCA9IDk2MCwgaGVpZ2h0ID0gNzIwKQ0KYGBgDQoNCiMjIENyw6lhdGlvbiBkZXMgZmVhdHVyZXMNCg0KTm91cyBwb3V2b25zIGNyw6llciBsZXMgZmVhdHVyZXMgw6AgcGFydGlyIGRlcyBmZWF0dXJlcyBzw6lsZWN0aW9ubsOpZXMuDQoNCmBgYHtyIEdlbmVyYXRpb25Eb25uZWVzNH0NCiMgQ29tcHRldXIgZGUgdGVtcHMNCkN1cnJlbnRUaW1lIDwtIHRpbWVyKCkgIyBQcsOpcGFyYXRpb24gZGUgbCfDqXZhbHVhdGlvbiBkZXMgbW9kw6hsZXMgYXZlYyBmZWF0dXJlcyBzw6lsZWN0aW9ubsOpZXMNCg0KIyBPw7kgc2F1dmVnYXJkZXIgbGVzIGZpY2hpZXJzID8NCmZpbGVfdGFnIDwtICI0X2RhdGEvIg0KDQojIEluaXRpYWxpc2F0aW9uIGRlIGxhIHZhcmlhYmxlIHF1aSBhY2N1ZWlsbGVyYSBsYSBwcsOpY2lzaW9uDQphY2N1cmFjeSA8LSBkYXRhLmZyYW1lKG1hdHJpeChucm93ID0gMTYsIG5jb2wgPSAxMykpDQpjb2xuYW1lcyhhY2N1cmFjeSkgPC0gYygiRm9sZCIsICJ4Z2JfTGluZWFyTW9kZWwiLCAieGdiX0RlY2lzaW9uVHJlZSIsICJ4Z2JfUmFuZG9tRm9yZXN0IiwgInhnYl9HcmFkaWVudEJvb3N0aW5nIiwgImgyb19MaW5lYXJNb2RlbCIsICJoMm9fRGVjaXNpb25UcmVlIiwgImgyb19SYW5kb21Gb3Jlc3QiLCAiaDJvX0dyYWRpZW50Qm9vc3RpbmciLCAiaDJvX05OXzMyeDZfUmVMVSIsICJoMm9fTk5fMzJ4Nl9Tb2Z0IiwgImgyb19OTl8xNngxNng2X1JlTFUiLCAiaDJvX05OXzE2eDE2eDZfU29mdCIpDQphY2N1cmFjeVssIDFdIDwtIGMoIkZvbGRfMXYyIiwgIkZvbGRfMXYzIiwgIkZvbGRfMnYxIiwgIkZvbGRfMnYzIiwgIkZvbGRfM3YxIiwgIkZvbGRfM3YyIiwgIkZvbGRfMXYyMyIsICJGb2xkXzJ2MTMiLCAiRm9sZF8zdjEyIiwgIkZvbGRfMTJ2MyIsICJGb2xkXzEzdjIiLCAiRm9sZF8yM3YxIiwgIk1veWVubmVfMWMxIiwgIk1veWVubmVfMWMyIiwgIk1veWVubmVfMmMxIiwgIk1veWVubmUiKQ0KDQojIEluaXRpYWxpc2F0aW9uIGRlcyBmb2xkcyBwb3VyIGxhIGNyb3NzLXZhbGlkYXRpb24NCmZvbGRzX3RyYWluIDwtIGxpc3QoKQ0KZm9sZHNfdGVzdCA8LSBsaXN0KCkNCnRyYWluaW5nX2RhdGEgPC0gbGlzdCgpDQp0ZXN0aW5nX2RhdGEgPC0gbGlzdCgpDQp0cmFpbmluZ194Z2IgPC0gbGlzdCgpDQp0ZXN0aW5nX3hnYiA8LSBsaXN0KCkNCnRyYWluaW5nX2gybyA8LSBsaXN0KCkNCnRlc3RpbmdfaDJvIDwtIGxpc3QoKQ0KY29tYmluYXRpb25zX3RyYWluIDwtIGMobGlzdCgxLCAxLCAyLCAyLCAzLCAzKSwgY29tYm4oMywgMSwgc2ltcGxpZnkgPSBGQUxTRSksIGNvbWJuKDMsIDIsIHNpbXBsaWZ5ID0gRkFMU0UpKQ0KY29tYmluYXRpb25zX3Rlc3QgPC0gYyhsaXN0KDIsIDMsIDEsIDMsIDEsIDIpLCByZXYoY29tYm4oMywgMiwgc2ltcGxpZnkgPSBGQUxTRSkpLCByZXYoY29tYm4oMywgMSwgc2ltcGxpZnkgPSBGQUxTRSkpKQ0KdGVtcF9mYWN0b3JzIDwtIGFzLmZhY3Rvcihncm91cF9wYXRoJHBhdGhfSUQpDQoNCiMgQ3LDqWF0aW9uIGRlcyBkb25uw6llcyBkJ2VudHJhaW5lbWVudCBldCBkZSB2YWxpZGF0aW9uDQpmb3IgKGkgaW4gMToxMikgew0KICANCiAgIyBDcsOpYXRpb24gZGVzIGZvbGRzIGQnZW50cmFpbmVtZW50IGV0IGRlIHZhbGlkYXRpb24NCiAgZm9sZHNfdHJhaW5bW2ldXSA8LSB3aGljaChncm91cF9yb29tW1siZGF0YXNldF9JRCJdXSAlaW4lIGNvbWJpbmF0aW9uc190cmFpbltbaV1dKQ0KICBmb2xkc190ZXN0W1tpXV0gPC0gd2hpY2goZ3JvdXBfcm9vbVtbImRhdGFzZXRfSUQiXV0gJWluJSBjb21iaW5hdGlvbnNfdGVzdFtbaV1dKQ0KICANCiAgIyBSZWNoZXJjaGUgZXQgc3VwcHJlc3Npb24gZHUgbGFiZWwgMyBsb3JzcXVlIGxhIHNhbGxlIDEgZXN0IGlzb2zDqWUgKHNvaXQgZW4gdHJhaW4gb24gZW5sw6h2ZSBlbiB0ZXN0LCBzb2l0IGVuIHRlc3Qgb24gZW5sw6h2ZSBlbiB0cmFpbikNCiAgaWYgKChsZW5ndGgoY29tYmluYXRpb25zX3RyYWluW1tpXV0pID09IDEpICYgKGNvbWJpbmF0aW9uc190cmFpbltbaV1dWzFdID09IDEpKSB7DQogICAgZm9sZHNfdGVzdFtbaV1dIDwtIGZvbGRzX3Rlc3RbW2ldXVtncm91cF9wYXRoJHBhdGhfSURbZm9sZHNfdGVzdFtbaV1dXSAhPSAzXQ0KICB9DQogIGlmICgobGVuZ3RoKGNvbWJpbmF0aW9uc190ZXN0W1tpXV0pID09IDEpICYgKGNvbWJpbmF0aW9uc190ZXN0W1tpXV1bMV0gPT0gMSkpIHsNCiAgICBmb2xkc190cmFpbltbaV1dIDwtIGZvbGRzX3RyYWluW1tpXV1bZ3JvdXBfcGF0aCRwYXRoX0lEW2ZvbGRzX3RyYWluW1tpXV1dICE9IDNdDQogIH0NCiAgDQogICMgQ3LDqWF0aW9uIGRlcyBkb25uw6llcyBkJ2VudHJhaW5lbWVudCBldCBkZSB2YWxpZGF0aW9uDQogIHRyYWluaW5nX2RhdGFbW2ldXSA8LSBtaW5pX2xtW2ZvbGRzX3RyYWluW1tpXV0sIHdoaWNoKGJlc3Rfd2VpZ2h0cyRvcHRpbWl6ZXIkZGlzY3JldGUgPT0gMSldDQogIHRlc3RpbmdfZGF0YVtbaV1dIDwtIG1pbmlfbG1bZm9sZHNfdGVzdFtbaV1dLCB3aGljaChiZXN0X3dlaWdodHMkb3B0aW1pemVyJGRpc2NyZXRlID09IDEpXQ0KICANCiAgIyBFbnJlZ2lzdHJlbWVudCBkZXMgZG9ubsOpZXMgQ1NWDQogIGZ3cml0ZSh0cmFpbmluZ19kYXRhW1tpXV0sIHBhc3RlMChmaWxlX3RhZywgInRyYWluTkxfIiwgc3ByaW50ZigiJTAyZCIsIGkpLCAiLmNzdiIpKQ0KICBmd3JpdGUodGVzdGluZ19kYXRhW1tpXV0sIHBhc3RlMChmaWxlX3RhZywgInRlc3ROTF8iLCBzcHJpbnRmKCIlMDJkIiwgaSksICIuY3N2IikpDQogIA0KICAjIFRyYW5zZm9ybWF0aW9uIGRlcyBkb25uw6llcyBhdSBmb3JtYXQgYXBwcm9wcmnDqSBwb3VyIHhnYm9vc3QNCiAgdHJhaW5pbmdfeGdiW1tpXV0gPC0geGdiLkRNYXRyaXgoZGF0YSA9IGFzLm1hdHJpeCh0cmFpbmluZ19kYXRhW1tpXV0pLCBsYWJlbCA9IGdyb3VwX3BhdGgkcGF0aF9JRFtmb2xkc190cmFpbltbaV1dXSAtIDEpDQogIHRlc3RpbmdfeGdiW1tpXV0gPC0geGdiLkRNYXRyaXgoZGF0YSA9IGFzLm1hdHJpeCh0ZXN0aW5nX2RhdGFbW2ldXSksIGxhYmVsID0gZ3JvdXBfcGF0aCRwYXRoX0lEW2ZvbGRzX3Rlc3RbW2ldXV0gLSAxKQ0KICANCiAgIyBEdW1waW5nIGRlcyBkYXRhc2V0cyBiaW5haXJlcyB4Z2Jvb3N0DQogIHhnYi5ETWF0cml4LnNhdmUodHJhaW5pbmdfeGdiW1tpXV0sIHBhc3RlMChmaWxlX3RhZywgInRyYWluTF8iLCBzcHJpbnRmKCIlMDJkIiwgaSksICIuZGF0YSIpKQ0KICB4Z2IuRE1hdHJpeC5zYXZlKHRlc3RpbmdfeGdiW1tpXV0sIHBhc3RlMChmaWxlX3RhZywgInRlc3RMXyIsIHNwcmludGYoIiUwMmQiLCBpKSwgIi5kYXRhIikpDQogIA0KICAjIFRyYW5zZm9ybWF0aW9uIGRlcyBkb25uw6llcyBhdSBmb3JtYXQgYXBwcm9wcmnDqSBwb3VyIEgyTw0KICB0cmFpbmluZ19oMm9bW2ldXSA8LSBhcy5oMm8oY2JpbmQoTGFiZWwgPSB0ZW1wX2ZhY3RvcnNbZm9sZHNfdHJhaW5bW2ldXV0sIHRyYWluaW5nX2RhdGFbW2ldXSkpDQogIHRlc3RpbmdfaDJvW1tpXV0gPC0gYXMuaDJvKGNiaW5kKExhYmVsID0gdGVtcF9mYWN0b3JzW2ZvbGRzX3Rlc3RbW2ldXV0sIHRlc3RpbmdfZGF0YVtbaV1dKSkNCiAgDQogICMgRW5yZWdpc3RyZW1lbnQgZGVzIGZyYW1lcyBIMk8gKENTViArIExhYmVsKQ0KICBoMm8uZXhwb3J0RmlsZSh0cmFpbmluZ19oMm9bW2ldXSwgcGFzdGUwKGZpbGVfdGFnLCAidHJhaW5MXyIsIHNwcmludGYoIiUwMmQiLCBpKSwgIi5jc3YiKSwgZm9yY2UgPSBUUlVFKQ0KICBoMm8uZXhwb3J0RmlsZSh0ZXN0aW5nX2gyb1tbaV1dLCBwYXN0ZTAoZmlsZV90YWcsICJ0ZXN0TF8iLCBzcHJpbnRmKCIlMDJkIiwgaSksICIuY3N2IiksIGZvcmNlID0gVFJVRSkNCiAgDQp9DQoNCiMgVGVtcHMgbsOpY2Vzc2FpcmUNCnRpbWluZyhDdXJyZW50VGltZSwgIlByw6lwYXJhdGlvbiBkZSBsJ8OpdmFsdWF0aW9uIGRlcyBtb2TDqGxlcyBhdmVjIGZlYXR1cmVzIHPDqWxlY3Rpb25uw6llcyIpDQpgYGANCg0KIyMgRW50cmFpbmVtZW50IGRlcyBkb3V6ZSBtb2TDqGxlcw0KDQpOb3VzIHBvdXZvbnMgbWFpbnRlbmFudCBlbnRyYWluZXIgbGVzIG1vZMOobGVzIHN1ciBub3RyZSBzw6lsZWN0aW9uIGRlIGZlYXR1cmVzLg0KDQpgYGB7ciBFbnRyYWluZW1lbnQ0LCBjYWNoZT1UUlVFfQ0KIyBDb21wdGV1ciBkZSB0ZW1wcw0KQ3VycmVudFRpbWUgPC0gdGltZXIoKSAjIENodW5rIENyw6lhdGlvbiBldCDDqXZhbHVhdGlvbiBkZXMgZG91emUgbW9kw6hsZXMgYXZlYyBmZWF0dXJlcyBzw6lsZWN0aW9ubsOpZXMNCg0KIyBPw7kgc2F1dmVnYXJkZXIgbGVzIGZpY2hpZXJzID8NCmZpbGVfdGFnIDwtICI0X21vZGVscy8iDQpmaWxlX2gybyA8LSAiNF9tb2RlbHMiDQoNCiMgQm91Y2xlIGQnw6l2YWx1YXRpb24NCmZvciAoaSBpbiAxOjEyKSB7DQogIA0KICAjIEVudHJhaW5lbWVudCBkdSBtb2TDqGxlIGRlIHLDqWdyZXNzaW9uIGxvZ2lzdGlxdWUgKHhnYm9vc3QpDQogIHRlbXBfbW9kZWwgPC0geGdiX2R5bmFtaWNfdHJhaW4odHJhaW4gPSB0cmFpbmluZ194Z2JbW2ldXSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0ZXN0ID0gdGVzdGluZ194Z2JbW2ldXSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBib29zdGVyID0gImdibGluZWFyIiwgIyBMaW7DqWFpcmUNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBucm91bmRzID0gMTAwMDAwMCwgIyBBcnLDqnTDqSBhdSBtZWlsbGV1ciByw6lzdWx0YXQNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBudW1fcGFyYWxsZWxfdHJlZXMgPSAxKQ0KICB4Z2IuZHVtcChtb2RlbCA9IHRlbXBfbW9kZWwsICMgTW9kw6hsZSDDoCBlbnJlZ2lzdHJlcg0KICAgICAgICAgICBmbmFtZSA9IHBhc3RlMChmaWxlX3RhZywgInhnYl9nbG1fIiwgc3ByaW50ZigiJTAyZCIsIGkpLCAiLmpzb24iKSwgIyBPw7kgZW5yZWdpc3RyZXIgbGUgbW9kw6hsZSA/DQogICAgICAgICAgIHdpdGhfc3RhdHMgPSBUUlVFLCAjIEVucmVnaXN0cmVtZW50IGRlcyBzdGF0aXN0aXF1ZXMgc2kgbW9kw6hsZSBnYnRyZWUNCiAgICAgICAgICAgZHVtcF9mb3JtYXQgPSAianNvbiIpICMgRHVtcCBhdSBmb3JtYXQganNvbiwgcsOpLXV0aWxpc2FibGUNCiAgYWNjdXJhY3lbaSwgMl0gPC0gMSAtIHRlbXBfbW9kZWwkZXZhbHVhdGlvbl9sb2dbWzJdXVt0ZW1wX21vZGVsJGJlc3RfaXRlcmF0aW9uXSAjIFLDqWN1cMOpcmF0aW9uIGR1IG1laWxsZXVyIHLDqXN1bHRhdA0KICANCiAgIyBFbnRyYWluZW1lbnQgZHUgbW9kw6hsZSBkJ2FyYnJlIGRlIGTDqWNpc2lvbiAoeGdib29zdCkNCiAgdGVtcF9tb2RlbCA8LSB4Z2JfZHluYW1pY190cmFpbih0cmFpbiA9IHRyYWluaW5nX3hnYltbaV1dLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRlc3QgPSB0ZXN0aW5nX3hnYltbaV1dLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJvb3N0ZXIgPSAiZ2J0cmVlIiwgIyBOb24tbGluw6lhaXJlDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbnJvdW5kcyA9IDEsICMgVW4gc2V1bCBhcmJyZQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG51bV9wYXJhbGxlbF90cmVlcyA9IDEpDQogIHhnYi5kdW1wKG1vZGVsID0gdGVtcF9tb2RlbCwgIyBNb2TDqGxlIMOgIGVucmVnaXN0cmVyDQogICAgICAgICAgIGZuYW1lID0gcGFzdGUwKGZpbGVfdGFnLCAieGdiX2R0XyIsIHNwcmludGYoIiUwMmQiLCBpKSwgIi5qc29uIiksICMgT8O5IGVucmVnaXN0cmVyIGxlIG1vZMOobGUgPw0KICAgICAgICAgICB3aXRoX3N0YXRzID0gVFJVRSwgIyBFbnJlZ2lzdHJlbWVudCBkZXMgc3RhdGlzdGlxdWVzIHNpIG1vZMOobGUgZ2J0cmVlDQogICAgICAgICAgIGR1bXBfZm9ybWF0ID0gImpzb24iKSAjIER1bXAgYXUgZm9ybWF0IGpzb24sIHLDqS11dGlsaXNhYmxlDQogIGFjY3VyYWN5W2ksIDNdIDwtIDEgLSB0ZW1wX21vZGVsJGV2YWx1YXRpb25fbG9nW1syXV1bMV0gIyBSw6ljdXDDqXJhdGlvbiBkdSBtZWlsbGV1ciByw6lzdWx0YXQNCiAgDQogICMgRW50cmFpbmVtZW50IGR1IG1vZMOobGUgZGUgUmFuZG9tIEZvcmVzdCAoeGdib29zdCkNCiAgdGVtcF9tb2RlbCA8LSB4Z2JfZHluYW1pY190cmFpbih0cmFpbiA9IHRyYWluaW5nX3hnYltbaV1dLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRlc3QgPSB0ZXN0aW5nX3hnYltbaV1dLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJvb3N0ZXIgPSAiZ2J0cmVlIiwgIyBOb24tbGluw6lhaXJlDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbnJvdW5kcyA9IDEsICMgVW5lIHNldWxlIGl0w6lyYXRpb24NCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBudW1fcGFyYWxsZWxfdHJlZXMgPSAyMDApICMgRGUgMjAwIGFyYnJlcw0KICB4Z2IuZHVtcChtb2RlbCA9IHRlbXBfbW9kZWwsICMgTW9kw6hsZSDDoCBlbnJlZ2lzdHJlcg0KICAgICAgICAgICBmbmFtZSA9IHBhc3RlMChmaWxlX3RhZywgInhnYl9yZl8iLCBzcHJpbnRmKCIlMDJkIiwgaSksICIuanNvbiIpLCAjIE/DuSBlbnJlZ2lzdHJlciBsZSBtb2TDqGxlID8NCiAgICAgICAgICAgd2l0aF9zdGF0cyA9IFRSVUUsICMgRW5yZWdpc3RyZW1lbnQgZGVzIHN0YXRpc3RpcXVlcyBzaSBtb2TDqGxlIGdidHJlZQ0KICAgICAgICAgICBkdW1wX2Zvcm1hdCA9ICJqc29uIikgIyBEdW1wIGF1IGZvcm1hdCBqc29uLCByw6ktdXRpbGlzYWJsZQ0KICBhY2N1cmFjeVtpLCA0XSA8LSAxIC0gdGVtcF9tb2RlbCRldmFsdWF0aW9uX2xvZ1tbMl1dICMgUsOpY3Vww6lyYXRpb24gZHUgbWVpbGxldXIgcsOpc3VsdGF0DQogIA0KICAjIEVudHJhaW5lbWVudCBkdSBtb2TDqGxlIGQnYXJicmUgZGUgZMOpY2lzaW9uIGJvb3N0w6kgYXZlYyBwcm90ZWN0aW9uIGNvbnRyZSBsJ292ZXJmaXR0aW5nICh4Z2Jvb3N0KQ0KICB0ZW1wX21vZGVsIDwtIHhnYl9keW5hbWljX3RyYWluKHRyYWluID0gdHJhaW5pbmdfeGdiW1tpXV0sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGVzdCA9IHRlc3RpbmdfeGdiW1tpXV0sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYm9vc3RlciA9ICJnYnRyZWUiLCAjIE5vbi1saW7DqWFpcmUNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBucm91bmRzID0gMTAwMDAwMCwgIyBBcnLDqnTDqSBhdSBtZWlsbGV1ciByw6lzdWx0YXQNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBudW1fcGFyYWxsZWxfdHJlZXMgPSAxKQ0KICB4Z2IuZHVtcChtb2RlbCA9IHRlbXBfbW9kZWwsICMgTW9kw6hsZSDDoCBlbnJlZ2lzdHJlcg0KICAgICAgICAgICBmbmFtZSA9IHBhc3RlMChmaWxlX3RhZywgInhnYl9nYnRfIiwgc3ByaW50ZigiJTAyZCIsIGkpLCAiLmpzb24iKSwgIyBPw7kgZW5yZWdpc3RyZXIgbGUgbW9kw6hsZSA/DQogICAgICAgICAgIHdpdGhfc3RhdHMgPSBUUlVFLCAjIEVucmVnaXN0cmVtZW50IGRlcyBzdGF0aXN0aXF1ZXMgc2kgbW9kw6hsZSBnYnRyZWUNCiAgICAgICAgICAgZHVtcF9mb3JtYXQgPSAianNvbiIpICMgRHVtcCBhdSBmb3JtYXQganNvbiwgcsOpLXV0aWxpc2FibGUNCiAgYWNjdXJhY3lbaSwgNV0gPC0gMSAtIHRlbXBfbW9kZWwkZXZhbHVhdGlvbl9sb2dbWzJdXVt0ZW1wX21vZGVsJGJlc3RfaXRlcmF0aW9uXSAjIFLDqWN1cMOpcmF0aW9uIGR1IG1laWxsZXVyIHLDqXN1bHRhdA0KICANCiAgIyBFbnRyYWluZW1lbnQgZHUgbW9kw6hsZSBkZSByw6lncmVzc2lvbiBsb2dpc3RpcXVlIChoMm8pDQogIHRlbXBfbW9kZWwgPC0gaDJvLmdsbSh5ID0gMSwNCiAgICAgICAgICAgICAgICAgICAgICAgIHRyYWluaW5nX2ZyYW1lID0gdHJhaW5pbmdfaDJvW1tpXV0sDQogICAgICAgICAgICAgICAgICAgICAgICB2YWxpZGF0aW9uX2ZyYW1lID0gdGVzdGluZ19oMm9bW2ldXSwNCiAgICAgICAgICAgICAgICAgICAgICAgIG1vZGVsX2lkID0gcGFzdGUwKCJoMm9fZ2xtXyIsIHNwcmludGYoIiUwMmQiLCBpKSksICMgTm9tIGR1IG1vZMOobGUNCiAgICAgICAgICAgICAgICAgICAgICAgIG1heF9pdGVyYXRpb25zID0gMTAwLCAjIDEwMCBpdMOpcmF0aW9ucyBkJ29wdGltaXNhdGlvbg0KICAgICAgICAgICAgICAgICAgICAgICAgc29sdmVyID0gIklSTFNNIiwgIyBTb2x2ZXVyIHBhciBkw6lmYXV0DQogICAgICAgICAgICAgICAgICAgICAgICBzdGFuZGFyZGl6ZSA9IEZBTFNFLCAjIFBhcyBkZSBzdGFuZGFyZGlzYXRpb24gcHVpc3F1ZSBbLTEsIDFdDQogICAgICAgICAgICAgICAgICAgICAgICBmYW1pbHkgPSAibXVsdGlub21pYWwiLCAjIENsYXNzaWZpY2F0aW9uIG11bHRpLWNsYXNzZQ0KICAgICAgICAgICAgICAgICAgICAgICAgc2VlZCA9IDAsICMgUmVwcm9kdWN0aW9uIGRlcyByw6lzdWx0YXRzDQogICAgICAgICAgICAgICAgICAgICAgICBpbnRlcmNlcHQgPSBUUlVFKQ0KICBoMm8uZG93bmxvYWRfcG9qbyh0ZW1wX21vZGVsLCAjIE1vZMOobGUgw6AgZW5yZWdpc3RyZXINCiAgICAgICAgICAgICAgICAgICAgcGF0aCA9IGZpbGVfaDJvLCAjIE/DuSBlbnJlZ2lzdHJlciBsZSBtb2TDqGxlID8NCiAgICAgICAgICAgICAgICAgICAgZ2V0X2phciA9IEZBTFNFKSAjIFBhcyBkZSBmaWNoaWVyIC5qYXINCiAgYWNjdXJhY3lbaSwgNl0gPC0gdGVtcF9tb2RlbEBtb2RlbCR2YWxpZGF0aW9uX21ldHJpY3NAbWV0cmljcyRoaXRfcmF0aW9fdGFibGVbMSwgMl0NCiAgDQogICMgRW50cmFpbmVtZW50IGR1IG1vZMOobGUgZCdhcmJyZSBkZSBkw6ljaXNpb24gKGgybykNCiAgdGVtcF9tb2RlbCA8LSBoMm8ucmFuZG9tRm9yZXN0KHkgPSAxLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdHJhaW5pbmdfZnJhbWUgPSB0cmFpbmluZ19oMm9bW2ldXSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHZhbGlkYXRpb25fZnJhbWUgPSB0ZXN0aW5nX2gyb1tbaV1dLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbW9kZWxfaWQgPSBwYXN0ZTAoImgyb19kdF8iLCBzcHJpbnRmKCIlMDJkIiwgaSkpLCAjIE5vbSBkdSBtb2TDqGxlDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzYW1wbGVfcmF0ZSA9IDEsICMgVG91dGVzIGxlcyBvYnNlcnZhdGlvbnMgc2Vyb250IHByaXNlcyBlbiBjb21wdGUgcG91ciBsZSBzZXVsIGFyYnJlIGRlIGTDqWNpc2lvbg0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbXRyaWVzID0gc3VtKGJlc3Rfd2VpZ2h0cyRvcHRpbWl6ZXIkZGlzY3JldGUpLCAjIFRvdXRlcyBsZXMgZmVhdHVyZXMgc2Vyb250IHByaXNlcyBlbiBjb21wdGUgcG91ciBsZSBzZXVsIGFyYnJlIGRlIGTDqWNpc2lvbg0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbnRyZWVzID0gMSwgIyBVbiBzZXVsIGFyYnJlDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZWVkID0gMCkgIyBSZXByb2R1Y3Rpb24gZGVzIHLDqXN1bHRhdHMNCiAgaDJvLmRvd25sb2FkX3Bvam8odGVtcF9tb2RlbCwgIyBNb2TDqGxlIMOgIGVucmVnaXN0cmVyDQogICAgICAgICAgICAgICAgICAgIHBhdGggPSBmaWxlX2gybywgIyBPw7kgZW5yZWdpc3RyZXIgbGUgbW9kw6hsZSA/DQogICAgICAgICAgICAgICAgICAgIGdldF9qYXIgPSBGQUxTRSkgIyBQYXMgZGUgZmljaGllciAuamFyDQogIGFjY3VyYWN5W2ksIDddIDwtIDEgLSBtaW4odGVtcF9tb2RlbEBtb2RlbCRzY29yaW5nX2hpc3RvcnkkdmFsaWRhdGlvbl9jbGFzc2lmaWNhdGlvbl9lcnJvciwgbmEucm0gPSBUUlVFKQ0KICANCiAgIyBFbnRyYWluZW1lbnQgZHUgbW9kw6hsZSBkZSBSYW5kb20gRm9yZXN0IChoMm8pDQogIHRlbXBfbW9kZWwgPC0gaDJvLnJhbmRvbUZvcmVzdCh5ID0gMSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRyYWluaW5nX2ZyYW1lID0gdHJhaW5pbmdfaDJvW1tpXV0sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB2YWxpZGF0aW9uX2ZyYW1lID0gdGVzdGluZ19oMm9bW2ldXSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1vZGVsX2lkID0gcGFzdGUwKCJoMm9fcmZfIiwgc3ByaW50ZigiJTAyZCIsIGkpKSwgIyBOb20gZHUgbW9kw6hsZQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2FtcGxlX3JhdGUgPSAwLjYzMiwgIyBCb290c3RyYXBwaW5nIC42MzIgcG91ciBjaGFxdWUgYXJicmUgZGUgZMOpY2lzaW9uDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtdHJpZXMgPSAtMSwgIyBzcXJ0KDM2KSBmZWF0dXJlcyBzZXJvbnQgcHJpc2VzIGVuIGNvbXB0ZSBwb3VyIGNoYXF1ZSBhcmJyZSBkZSBkw6ljaXNpb24NCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG50cmVlcyA9IDIwMCwgIyAyMDAgYXJicmVzDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZWVkID0gMCkgIyBSZXByb2R1Y3Rpb24gZGVzIHLDqXN1bHRhdHMNCiAgaDJvLmRvd25sb2FkX3Bvam8odGVtcF9tb2RlbCwgIyBNb2TDqGxlIMOgIGVucmVnaXN0cmVyDQogICAgICAgICAgICAgICAgICAgIHBhdGggPSBmaWxlX2gybywgIyBPw7kgZW5yZWdpc3RyZXIgbGUgbW9kw6hsZSA/DQogICAgICAgICAgICAgICAgICAgIGdldF9qYXIgPSBGQUxTRSkgIyBQYXMgZGUgZmljaGllciAuamFyDQogIGFjY3VyYWN5W2ksIDhdIDwtIDEgLSBtaW4odGVtcF9tb2RlbEBtb2RlbCRzY29yaW5nX2hpc3RvcnkkdmFsaWRhdGlvbl9jbGFzc2lmaWNhdGlvbl9lcnJvciwgbmEucm0gPSBUUlVFKQ0KICANCiAgIyBFbnRyYWluZW1lbnQgZHUgbW9kw6hsZSBkJ2FyYnJlIGRlIGTDqWNpc2lvbiBib29zdMOpIGF2ZWMgcHJvdGVjdGlvbiBjb250cmUgbCdvdmVyZml0dGluZyAoaDJvKQ0KICB0ZW1wX21vZGVsIDwtIGgyby5nYm0oeSA9IDEsDQogICAgICAgICAgICAgICAgICAgICAgdHJhaW5pbmdfZnJhbWUgPSB0cmFpbmluZ19oMm9bW2ldXSwNCiAgICAgICAgICAgICAgICAgICAgICB2YWxpZGF0aW9uX2ZyYW1lID0gdGVzdGluZ19oMm9bW2ldXSwNCiAgICAgICAgICAgICAgICAgICAgICBtb2RlbF9pZCA9IHBhc3RlMCgiaDJvX2didF8iLCBzcHJpbnRmKCIlMDJkIiwgaSkpLCAjIE5vbSBkdSBtb2TDqGxlDQogICAgICAgICAgICAgICAgICAgICAgZGlzdHJpYnV0aW9uID0gIm11bHRpbm9taWFsIiwgIyBDbGFzc2lmaWNhdGlvbiBtdWx0aS1jbGFzc2UNCiAgICAgICAgICAgICAgICAgICAgICBzYW1wbGVfcmF0ZSA9IDEsICMgUGFzIGRlIHByb2Nlc3N1cyBzdG9jaGFzdGlxdWUNCiAgICAgICAgICAgICAgICAgICAgICBudHJlZXMgPSAxMDAsICMgMTAwIGl0w6lyYXRpb25zIGRlIGJvb3N0aW5nIGF1IG1heGltdW0NCiAgICAgICAgICAgICAgICAgICAgICBzY29yZV9lYWNoX2l0ZXJhdGlvbiA9IFRSVUUsICMgTm90ZXIgbGEgdmFsZXVyIGRlIGNoYXF1ZSBpdMOpcmF0aW9uDQogICAgICAgICAgICAgICAgICAgICAgc3RvcHBpbmdfcm91bmRzID0gMTAsICMgQXJyw6p0IGFwcsOocyAxMCBpdMOpcmF0aW9ucyBzYW5zIGFtw6lsaW9yYXRvbiBkZSBsYSBtw6l0cmlxdWUNCiAgICAgICAgICAgICAgICAgICAgICBzdG9wcGluZ19tZXRyaWMgPSAibWlzY2xhc3NpZmljYXRpb24iLCAjIFN1cnZlaWxsZXIgbCdpbmV4YWN0aXR1ZGUgZGUgbGEgY2xhc3NpZmljYXRpb24gcG91ciBsJ2FycsOqdA0KICAgICAgICAgICAgICAgICAgICAgIHN0b3BwaW5nX3RvbGVyYW5jZSA9IDAuMDAwMDEsICMgQXJyw6p0ZXIgbG9yc3F1ZSBsYSBtw6l0cmlxdWUgc3RhZ25lIGRlIDAuMDAxJQ0KICAgICAgICAgICAgICAgICAgICAgIHNlZWQgPSAwKSAjIFJlcHJvZHVjdGlvbiBkZXMgcsOpc3VsdGF0cw0KICBoMm8uZG93bmxvYWRfcG9qbyh0ZW1wX21vZGVsLCAjIE1vZMOobGUgw6AgZW5yZWdpc3RyZXINCiAgICAgICAgICAgICAgICAgICAgcGF0aCA9IGZpbGVfaDJvLCAjIE/DuSBlbnJlZ2lzdHJlciBsZSBtb2TDqGxlID8NCiAgICAgICAgICAgICAgICAgICAgZ2V0X2phciA9IEZBTFNFKSAjIFBhcyBkZSBmaWNoaWVyIC5qYXINCiAgYWNjdXJhY3lbaSwgOV0gPC0gMSAtIG1pbih0ZW1wX21vZGVsQG1vZGVsJHNjb3JpbmdfaGlzdG9yeSR2YWxpZGF0aW9uX2NsYXNzaWZpY2F0aW9uX2Vycm9yLCBuYS5ybSA9IFRSVUUpDQogIA0KICAjIEVudHJhaW5lbWVudCBkdSByw6lzZWF1IGRlIG5ldXJvbmVzIMOgIGFyY2hpdGVjdHVyZSAzMng2ICsgUmVMVSAoaDJvKQ0KICB0ZW1wX21vZGVsIDwtIGgyb19ubl90cmFpbih0cmFpbiA9IHRyYWluaW5nX2gyb1tbaV1dLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0ZXN0ID0gdGVzdGluZ19oMm9bW2ldXSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbW9kZWxfaWQgPSBwYXN0ZTAoImgyb19ubl8zMng2X1JlTFVfIiwgc3ByaW50ZigiJTAyZCIsIGkpKSwgIyBOb20gZHUgbW9kw6hsZQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhY3RpdmF0aW9uID0gIlJlY3RpZmllciIsICMgUmVMVQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBoaWRkZW4gPSAzMikgIyBBcmNoaXRlY3R1cmUgMzJ4Ng0KICBoMm8uZG93bmxvYWRfcG9qbyh0ZW1wX21vZGVsLCAjIE1vZMOobGUgw6AgZW5yZWdpc3RyZXINCiAgICAgICAgICAgICAgICAgICAgcGF0aCA9IGZpbGVfaDJvLCAjIE/DuSBlbnJlZ2lzdHJlciBsZSBtb2TDqGxlID8NCiAgICAgICAgICAgICAgICAgICAgZ2V0X2phciA9IEZBTFNFKSAjIFBhcyBkZSBmaWNoaWVyIC5qYXINCiAgYWNjdXJhY3lbaSwgMTBdIDwtIDEgLSBtaW4odGVtcF9tb2RlbEBtb2RlbCRzY29yaW5nX2hpc3RvcnkkdmFsaWRhdGlvbl9jbGFzc2lmaWNhdGlvbl9lcnJvciwgbmEucm0gPSBUUlVFKQ0KICANCiAgIyBFbnRyYWluZW1lbnQgZHUgcsOpc2VhdSBkZSBuZXVyb25lcyDDoCBhcmNoaXRlY3R1cmUgMzJ4NiArIFRhbmggKGgybykNCiAgdGVtcF9tb2RlbCA8LSBoMm9fbm5fdHJhaW4odHJhaW4gPSB0cmFpbmluZ19oMm9bW2ldXSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGVzdCA9IHRlc3RpbmdfaDJvW1tpXV0sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1vZGVsX2lkID0gcGFzdGUwKCJoMm9fbm5fMzJ4Nl9UYW5oXyIsIHNwcmludGYoIiUwMmQiLCBpKSksICMgTm9tIGR1IG1vZMOobGUNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYWN0aXZhdGlvbiA9ICJUYW5oIiwgIyAiU2lnbW9pZGUiDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhpZGRlbiA9IDMyKSAjIEFyY2hpdGVjdHVyZSAzMng2DQogIGgyby5kb3dubG9hZF9wb2pvKHRlbXBfbW9kZWwsICMgTW9kw6hsZSDDoCBlbnJlZ2lzdHJlcg0KICAgICAgICAgICAgICAgICAgICBwYXRoID0gZmlsZV9oMm8sICMgT8O5IGVucmVnaXN0cmVyIGxlIG1vZMOobGUgPw0KICAgICAgICAgICAgICAgICAgICBnZXRfamFyID0gRkFMU0UpICMgUGFzIGRlIGZpY2hpZXIgLmphcg0KICBhY2N1cmFjeVtpLCAxMV0gPC0gMSAtIG1pbih0ZW1wX21vZGVsQG1vZGVsJHNjb3JpbmdfaGlzdG9yeSR2YWxpZGF0aW9uX2NsYXNzaWZpY2F0aW9uX2Vycm9yLCBuYS5ybSA9IFRSVUUpDQogIA0KICAjIEVudHJhaW5lbWVudCBkdSByw6lzZWF1IGRlIG5ldXJvbmVzIMOgIGFyY2hpdGVjdHVyZSAxNngxNng2ICsgUmVMVSAoaDJvKQ0KICB0ZW1wX21vZGVsIDwtIGgyb19ubl90cmFpbih0cmFpbiA9IHRyYWluaW5nX2gyb1tbaV1dLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0ZXN0ID0gdGVzdGluZ19oMm9bW2ldXSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbW9kZWxfaWQgPSBwYXN0ZTAoImgyb19ubl8xNngxNng2X1JlTFVfIiwgc3ByaW50ZigiJTAyZCIsIGkpKSwgIyBOb20gZHUgbW9kw6hsZQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhY3RpdmF0aW9uID0gIlJlY3RpZmllciIsICMgUmVMVQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBoaWRkZW4gPSBjKDE2LCAxNikpICMgQXJjaGl0ZWN0dXJlIDE2eDE2eDYNCiAgaDJvLmRvd25sb2FkX3Bvam8odGVtcF9tb2RlbCwgIyBNb2TDqGxlIMOgIGVucmVnaXN0cmVyDQogICAgICAgICAgICAgICAgICAgIHBhdGggPSBmaWxlX2gybywgIyBPw7kgZW5yZWdpc3RyZXIgbGUgbW9kw6hsZSA/DQogICAgICAgICAgICAgICAgICAgIGdldF9qYXIgPSBGQUxTRSkgIyBQYXMgZGUgZmljaGllciAuamFyDQogIGFjY3VyYWN5W2ksIDEyXSA8LSAxIC0gbWluKHRlbXBfbW9kZWxAbW9kZWwkc2NvcmluZ19oaXN0b3J5JHZhbGlkYXRpb25fY2xhc3NpZmljYXRpb25fZXJyb3IsIG5hLnJtID0gVFJVRSkNCiAgDQogICMgRW50cmFpbmVtZW50IGR1IHLDqXNlYXUgZGUgbmV1cm9uZXMgw6AgYXJjaGl0ZWN0dXJlIDE2eDE2eDYgKyBUYW5oIChoMm8pDQogIHRlbXBfbW9kZWwgPC0gaDJvX25uX3RyYWluKHRyYWluID0gdHJhaW5pbmdfaDJvW1tpXV0sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRlc3QgPSB0ZXN0aW5nX2gyb1tbaV1dLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtb2RlbF9pZCA9IHBhc3RlMCgiaDJvX25uXzE2eDE2eDZfVGFuaF8iLCBzcHJpbnRmKCIlMDJkIiwgaSkpLCAjIE5vbSBkdSBtb2TDqGxlDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFjdGl2YXRpb24gPSAiVGFuaCIsICMgIlNpZ21vaWRlIg0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBoaWRkZW4gPSBjKDE2LCAxNikpICMgQXJjaGl0ZWN0dXJlIDE2eDE2eDYNCiAgaDJvLmRvd25sb2FkX3Bvam8odGVtcF9tb2RlbCwgIyBNb2TDqGxlIMOgIGVucmVnaXN0cmVyDQogICAgICAgICAgICAgICAgICAgIHBhdGggPSBmaWxlX2gybywgIyBPw7kgZW5yZWdpc3RyZXIgbGUgbW9kw6hsZSA/DQogICAgICAgICAgICAgICAgICAgIGdldF9qYXIgPSBGQUxTRSkgIyBQYXMgZGUgZmljaGllciAuamFyDQogIGFjY3VyYWN5W2ksIDEzXSA8LSAxIC0gbWluKHRlbXBfbW9kZWxAbW9kZWwkc2NvcmluZ19oaXN0b3J5JHZhbGlkYXRpb25fY2xhc3NpZmljYXRpb25fZXJyb3IsIG5hLnJtID0gVFJVRSkNCiAgDQp9DQoNCiMgVGVtcHMgbsOpY2Vzc2FpcmUNCnRpbWluZyhDdXJyZW50VGltZSwgIkNyw6lhdGlvbiBldCDDqXZhbHVhdGlvbiBkZXMgZG91emUgbW9kw6hsZXMgYXZlYyBmZWF0dXJlcyBzw6lsZWN0aW9ubsOpZXMiKQ0KYGBgDQoNCiMjIEFmZmljaGFnZSBkZXMgcsOpc3VsdGF0cw0KDQpMZXMgbW9kw6hsZXMgc29udCBiaWVuIHBsdXMgcGVyZm9ybWFudHMgcXUnaW5pdGlhbGVtZW50IGFwcsOocyB1bmUgc8OpbGVjdGlvbiBkZXMgZmVhdHVyZXMuIE9uIGF0dGVpbnQgZMOpc29ybWFpcyB1bmUgbW95ZW5uZSBoYXV0ZSBkJ2V4YWN0aXR1ZGUgZGUgNjglLCBjb250cmUgNjMlIGF1cGFyYXZhbnQuIEMnZXN0IHVuIGF2YW5jZW1lbnQgaW1wb3J0YW50IGRhbnMgbGEgcGVyZm9ybWFuY2UgZGVzIG1vZMOobGVzLCBldCBvbiBzYWl0IHF1J29uIHBldXQgZW5jb3JlIGFqb3V0ZXIgMy00JSBkZSBwZXJmb3JtYW5jZSBhdXggbW9kw6hsZXMgbGVzIHBsdXMgcGVyZm9ybWFudHMgYXZlYyB1biB0dW5pbmcgZGVzIGh5cGVycGFyYW3DqHRyZXMuDQoNCk9uIHJlbWFycXVlIGxlcyBtw6ptZXMgbW9kw6hsZXMgc29udCB0b3Vqb3VycyBsZXMgcGx1cyBwZXJmb3JtYW50cyA6IGNlIHNvbnQgbGVzIG1vZMOobGVzIGxpbsOpYWlyZXMgIQ0KDQpgYGB7ciBSZXN1bHRhdHM0fQ0KIyBNb3llbm5lIGRlcyByw6lzdWx0YXRzDQpmb3IgKGkgaW4gMjoxMykgew0KICBhY2N1cmFjeVsxMywgaV0gPC0gbWVhbihhY2N1cmFjeVsxOjYsIGldKQ0KICBhY2N1cmFjeVsxNCwgaV0gPC0gbWVhbihhY2N1cmFjeVs3OjksIGldKQ0KICBhY2N1cmFjeVsxNSwgaV0gPC0gbWVhbihhY2N1cmFjeVsxMDoxMiwgaV0pDQogIGFjY3VyYWN5WzE2LCBpXSA8LSBtZWFuKGFjY3VyYWN5WzEzOjE1LCBpXSkNCn0NCg0KIyBFbnJlZ2lzdHJlbWVudCBkZXMgc2NvcmVzDQpmd3JpdGUoYWNjdXJhY3ksICJzY29yZXMvNF9tb2RlbHMuY3N2IikNCg0KIyBBZmZpY2hhZ2UgZGVzIHLDqXN1bHRhdHMgZGFucyB1biB0YWJsZWF1IGludGVyYWN0aWYNCnRvX3ByaW50IDwtIGRhdGEudGFibGUodChhY2N1cmFjeVsxMzoxNiwgLTFdKSkgIyBQcsOpcGFyYXRpb24gZGVzIGRvbm7DqWVzIMOgIG1ldHRyZSBzdXIgdGFibGUNCmNvbG5hbWVzKHRvX3ByaW50KSA8LSBjKCIxIGNvbnRyZSAxIiwgIjEgY29udHJlIDIiLCAiMiBjb250cmUgMSIsICJNb3llbm5lIikgIyBSZW1pc2UgZGVzIG5vbXMgZGVzIGNvbG9ubmVzDQpyb3cubmFtZXModG9fcHJpbnQpIDwtIGNvbG5hbWVzKGFjY3VyYWN5KVstMV0gIyBSZW1pc2UgZGVzIG5vbXMgZGVzIGxpZ25lcw0KZGF0YXRhYmxlKHRvX3ByaW50LA0KICAgICAgICAgIGZpbHRlciA9ICJ0b3AiLCAjIEZpbHRyYWdlIGF1LWRlc3N1cyBkZSBsYSB0YWJsZQ0KICAgICAgICAgIGNsYXNzID0gImNlbGwtYm9yZGVyIHN0cmlwZSIsICMgQ1NTDQogICAgICAgICAgZXh0ZW5zaW9ucyA9IGMoIkNvbFJlb3JkZXIiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICJSb3dSZW9yZGVyIiksICMgUmVvcmRvbm5lciBtYW51ZWxsZW1lbnQgw6AgbGEgbWFpbg0KICAgICAgICAgIG9wdGlvbnMgPSBsaXN0KHBhZ2VMZW5ndGggPSAxMiwgIyBQYWdlIGFmZmljaGFudCAxMiBsaWduZXMNCiAgICAgICAgICAgICAgICAgICAgICAgICBvcmRlciA9IGxpc3QobGlzdCg0LCAiZGVzYyIpKSwgIyBPcmRvbm5lciBwYXIgZMOpZmF1dCBwYXIgbCdleGFjdGl0dWRlIG1veWVubmUNCiAgICAgICAgICAgICAgICAgICAgICAgICBjb2xSZW9yZGVyID0gVFJVRSwgIyBQbHVnaW4NCiAgICAgICAgICAgICAgICAgICAgICAgICByb3dSZW9yZGVyID0gVFJVRSkpICU+JSAjIFBsdWdpbg0KICBmb3JtYXRTdHlsZShjKCIxIGNvbnRyZSAxIiwgIjEgY29udHJlIDIiLCAiMiBjb250cmUgMSIpLA0KICAgICAgICAgICAgICAgICAgYmFja2dyb3VuZCA9IHN0eWxlQ29sb3JCYXIoYygwLCAxKSwgJ2xpZ2h0Z3JlZW4nKSwgIyBDb3VsZXVyIHZlcnQgY2xhaXIgcG91ciBsZXMgbcOpdHJpcXVlcyBwYXIgZm9sZA0KICAgICAgICAgICAgICAgICAgYmFja2dyb3VuZFNpemUgPSAnMTAwJSA5MCUnLA0KICAgICAgICAgICAgICAgICAgYmFja2dyb3VuZFJlcGVhdCA9ICduby1yZXBlYXQnLA0KICAgICAgICAgICAgICAgICAgYmFja2dyb3VuZFBvc2l0aW9uID0gJ2NlbnRlcicpICU+JQ0KICBmb3JtYXRTdHlsZSgiTW95ZW5uZSIsDQogICAgICAgICAgICAgIGJhY2tncm91bmQgPSBzdHlsZUNvbG9yQmFyKGMoMCwgMSksICdwaW5rJyksICMgQ291bGV1ciByb3NlIHBvdXIgbGEgbcOpdHJpcXVlIGRlIG1veWVubmUNCiAgICAgICAgICAgICAgYmFja2dyb3VuZFNpemUgPSAnMTAwJSA5MCUnLA0KICAgICAgICAgICAgICBiYWNrZ3JvdW5kUmVwZWF0ID0gJ25vLXJlcGVhdCcsDQogICAgICAgICAgICAgIGJhY2tncm91bmRQb3NpdGlvbiA9ICdjZW50ZXInKSAlPiUNCiAgZm9ybWF0UGVyY2VudGFnZShjb2x1bW5zID0gYygiMSBjb250cmUgMSIsICIxIGNvbnRyZSAyIiwgIjIgY29udHJlIDEiKSwNCiAgICAgICAgICAgICAgZGlnaXRzID0gOCkgJT4lDQogIGZvcm1hdFBlcmNlbnRhZ2UoY29sdW1ucyA9ICJNb3llbm5lIiwNCiAgICAgICAgICAgICAgZGlnaXRzID0gOCkNCg0KIyBBZmZpY2hhZ2UgZGVzIHLDqXN1bHRhdHMgZGFucyB1biB0YWJsZWF1IHN0YXRpcXVlDQpmb3JtYXR0YWJsZShhY2N1cmFjeVssIGMoMSwgMjo1KV0sIGxpc3QoZm9ybWF0dGFibGU6OmFyZWEoY29sID0geGdiX0xpbmVhck1vZGVsOnhnYl9HcmFkaWVudEJvb3N0aW5nKSB+IGNvbG9yX2Jhcigib3JhbmdlIikpKQ0KZm9ybWF0dGFibGUoYWNjdXJhY3lbLCBjKDEsIDY6OSldLCBsaXN0KGZvcm1hdHRhYmxlOjphcmVhKGNvbCA9IGgyb19MaW5lYXJNb2RlbDpoMm9fR3JhZGllbnRCb29zdGluZykgfiBjb2xvcl9iYXIoImN5YW4iKSkpDQpmb3JtYXR0YWJsZShhY2N1cmFjeVssIGMoMSwgMTA6MTMpXSwgbGlzdChmb3JtYXR0YWJsZTo6YXJlYShjb2wgPSBoMm9fTk5fMzJ4Nl9SZUxVOmgyb19OTl8xNngxNng2X1NvZnQpIH4gY29sb3JfYmFyKCJ5ZWxsb3ciKSkpDQpgYGANCg0KIyMgQW5hbHlzZSBkdSBtb2TDqGxlIGRlIHLDqWdyZXNzaW9uIGxvZ2lzdGlxdWUNCg0KTG9yc3F1J29uIHV0aWxpc2UgbGVzIG1laWxsZXVycyBwYXJhbcOodHJlcyBwb3VyIGxlIG1vZMOobGUgZGUgcsOpZ3Jlc3Npb24gbG9naXN0aXF1ZSwgb24gb2J0aWVudCBsZXMgcsOpc3VsdGF0cyBjaS1kZXNzb3VzLg0KDQpMYSBzYWxsZSAxIHNlbWJsZSB0b3Vqb3VycyBjYXVzZXIgZGVzIHByb2Jsw6htZXMsIGlsIGVzdCBwb3NzaWJsZSBxdSdpbCB5IGEgdG91am91cnMgdW4gbWF1dmFpcyBjb25kaXRpb25uZW1lbnQgZGVzIGRvbm7DqWVzIGlzc3VlcyBkZSBjZXR0ZSBzYWxsZS4gTGEgc2FsbGUgMiBldCAzIHNlbWJsZW50IGxlcyBwbHVzIHN0YWJsZXMsIGV0IHNlbWJsZSBpbmZvcm1lciBxdWUgOg0KDQotIEwndXRpbGlzYXRpb24gZGVzIDQgYW5jcmVzIHBlcm1ldCBkJ29idGVuaXIgbGUgcGx1cyBkJ2luZm9ybWF0aW9uLCBjZSBxdWkgYSDDqXTDqSBlbXBpcmlxdWVtZW50IHbDqXJpZmnDqSBwYXIgQmFjY2l1IGV0IGFsLiBkYW5zICpBbiBleHBlcmltZW50YWwgY2hhcmFjdGVyaXphdGlvbiBvZiByZXNlcnZvaXIgY29tcHV0aW5nIGluIGFtYmllbnQgYXNzaXN0ZWQgbGl2aW5nIGFwcGxpY2F0aW9ucyosIGV0IHbDqXJpZmnDqSDDqWdhbGVtZW50IGljaSAocHLDqXNlbmNlIGRlIHRvdXRlcyBsZXMgYW5jcmVzIGRhbnMgbGVzIGZlYXR1cmVzIHPDqWxlY3Rpb25uw6llcykNCi0gTGVzIGZlYXR1cmVzIHBldXZlbnQgw6p0cmUgZW4gY29udHJhZGljdGlvbiBzZWxvbiBsZXMgc2FsbGVzIChwYXIgZXhlbXBsZSwgUmVzaTFfMSBzZW1ibGUgaW51dGlsZSBwb3VyIGxlIGxhYmVsIDMgZGUgbGEgc2FsbGUgMiBhdmVjIHVuIGNvZWZmaWNpZW50IHF1YXNpbWVudCDDoCAwLCBtYWlzIGludMOpZ3JhbGVtZW50IHV0aWxlIHBvdXIgbGUgbcOqbWUgbGFiZWwgcG91ciBsYSBzYWxsZSAzKQ0KLSBMJ2VudHJhaW5lbWVudCBkZSBsYSBzYWxsZSAxIGVzdCBzaSByYXBpZGUgcXVlIGwnb3ZlcmZpdHRpbmcgYXBwYXJhaXQgaW1tw6lkaWF0ZW1lbnQgc2kgbGUgbm9tYnJlIGQnaXTDqXJhdGlvbnMgYXVnbWVudGUgw6AgdW4gbm9tYnJlIHN1cMOpcmlldXIgw6AgMSBjaGlmZnJlLg0KDQpJbCBlc3QgY2xhaXIgw6lnYWxlbWVudCBxdWUgbGEgY2hlbWluIDMgYSDDqXTDqSBlbnRpw6hyZW1lbnQgcGVyZHVlIGVuIGZhdmV1ciBkZSB0b3V0ZXMgbGVzIGF1dHJlcyBzYWxsZXMuIENlbGEgZXN0IHVuZSBib25uZSBldCB1bmUgbWF1dmFpc2UgY2hvc2UsIGNhciBsZSBtb2TDqGxlIHNlbWJsZSBwcml2aWzDqWdpZXIgbGEgcHLDqWRpY3Rpb24gZGUgbGEgY2hlbWluIDEgZW4gdG91dCBwb2ludCAoMzUlIGRlcyBwcsOpZGljdGlvbnMgcG91ciB1bmlxdWVtZW50IDI1JSBkZXMgdmFsZXVycykuIElsIGVzdCB0b3V0IMOgIGZhaXQgcGxhdXNpYmxlIHF1ZSBsZSBjaGVtaW4gMyBzb2l0IGRpZmZpY2lsZSDDoCBwcsOpZGlyZSBldCBxdWUgbCdvcHRpbWlzYXRpb24gYSBwcml2aWzDqWdpw6kgbGVzIGF1dHJlcyBjaGVtaW5zLCBwdWlzcXUnaWxzIHBvdXJyYWllbnQgw6p0cmUgcGx1cyBmYWNpbGUgw6Agb3B0aW1pc2VyIGxpbsOpYWlyZW1lbnQuDQoNCmBgYHtyIEFuYWx5c2VMb2dpc3RpcXVlNCwgd2FybmluZ3MgPSBGQUxTRX0NCiMgQ29tcHRldXIgZGUgdGVtcHMNCkN1cnJlbnRUaW1lIDwtIHRpbWVyKCkgIyBDaHVuayBQcsOpcGFyYXRpb24gZGUgbCdhbmFseXNlIGR1IGRlcm5pZXIgbW9kw6hsZSBkZSByw6lncmVzc2lvbiBsb2dpc3RpcXVlDQoNCiMgUHLDqS1pbml0aWFsaXNhdGlvbiBkZXMgdmFyaWFibGVzDQpwcmVkaWN0ZWRWYWx1ZXMgPC0gbWF0cml4KG5yb3cgPSAzMTQsIG5jb2wgPSA2KQ0KZXZvbHV0aW9uIDwtIGxpc3QoKQ0KdGVtcF9kdCA8LSBsaXN0KCkNCnRlbXBfbWVhbnMgPC0gZGF0YS5mcmFtZShGZWF0dXJlID0gYyhwYXN0ZTAocmVwKGMocGFzdGUwKCJDb2VmIiwgMTo0KSwgcGFzdGUwKCJSw6lzaSIsIDE6NCkpLCA0KSwgcGFzdGUwKCJfIiwgaW52ZXJzZS5ybGUobGlzdChsZW5ndGhzID0gcmVwKDgsIDQpLCB2YWx1ZXMgPSAxOjQpKSkpLCBwYXN0ZTAoIlBvc0luaXRpYWxlXyIsIDE6NCkpW3doaWNoKGJlc3Rfd2VpZ2h0cyRvcHRpbWl6ZXIkZGlzY3JldGUgPT0gMSldLA0KICAgICAgICAgICAgICAgICAgICAgICAgIEZvbGRfMSA9IG51bWVyaWMoc3VtKGJlc3Rfd2VpZ2h0cyRvcHRpbWl6ZXIkZGlzY3JldGUgPT0gMSkpLA0KICAgICAgICAgICAgICAgICAgICAgICAgIEZvbGRfMiA9IG51bWVyaWMoc3VtKGJlc3Rfd2VpZ2h0cyRvcHRpbWl6ZXIkZGlzY3JldGUgPT0gMSkpLA0KICAgICAgICAgICAgICAgICAgICAgICAgIEZvbGRfMyA9IG51bWVyaWMoc3VtKGJlc3Rfd2VpZ2h0cyRvcHRpbWl6ZXIkZGlzY3JldGUgPT0gMSkpLA0KICAgICAgICAgICAgICAgICAgICAgICAgIEZvbGRfTWVhbiA9IG51bWVyaWMoc3VtKGJlc3Rfd2VpZ2h0cyRvcHRpbWl6ZXIkZGlzY3JldGUgPT0gMSkpLA0KICAgICAgICAgICAgICAgICAgICAgICAgIEZlYXR1cmVfTWVhbiA9IG51bWVyaWMoc3VtKGJlc3Rfd2VpZ2h0cyRvcHRpbWl6ZXIkZGlzY3JldGUgPT0gMSkpLA0KICAgICAgICAgICAgICAgICAgICAgICAgIEZlYXR1cmVfU0QgPSBudW1lcmljKHN1bShiZXN0X3dlaWdodHMkb3B0aW1pemVyJGRpc2NyZXRlID09IDEpKSkNCg0KIyBCb3VjbGUgZCdlbnRyYWluZW1lbnQgMiBjb250cmUgMQ0KZm9yIChpIGluIDEwOjEyKSB7DQogIA0KICAjIEVudHJhaW5lbWVudCBkJ3VuIG1vZMOobGUgbGluw6lhaXJlDQogIHRlbXBfbW9kZWwgPC0geGdiLnRyYWluKGRhdGEgPSB0cmFpbmluZ194Z2JbW2ldXSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgd2F0Y2hsaXN0ID0gbGlzdCh0ZXN0ID0gdGVzdGluZ194Z2JbW2ldXSksICMgRG9ubsOpZXMgZGUgdmFsaWRhdGlvbg0KICAgICAgICAgICAgICAgICAgICAgICAgICBudW1fY2xhc3MgPSA2LCAjIDYgY2xhc3NlIGRlIGNsYXNzaWZpY2F0aW9uDQogICAgICAgICAgICAgICAgICAgICAgICAgIG50aHJlYWQgPSAxLCAjIDEgdGhyZWFkIHBvdXIgbGEgcmVwcm9kdWN0aW9uIGRlcyByw6lzdWx0YXRzDQogICAgICAgICAgICAgICAgICAgICAgICAgIG5yb3VuZHMgPSAxMDAwLCAjIDEwMDAgaXTDqXJhdGlvbnMsIG/DuSBhcnLDqnQgcHLDqW1hdHVyw6kNCiAgICAgICAgICAgICAgICAgICAgICAgICAgYWxwaGEgPSBiZXN0X3dlaWdodHMkb3B0aW1pemVyJGNvbnRpbnVvdXNbMV0sICMgUsOpZ3VsYXJpc2F0aW9uIEwxIChMYXNzbykNCiAgICAgICAgICAgICAgICAgICAgICAgICAgbGFtYmRhID0gYmVzdF93ZWlnaHRzJG9wdGltaXplciRjb250aW51b3VzWzJdLCAjIFLDqWd1bGFyaXNhdGlvbiBMMiAoUmlkZ2UpDQogICAgICAgICAgICAgICAgICAgICAgICAgIGxhbWJkYV9iaWFzID0gYmVzdF93ZWlnaHRzJG9wdGltaXplciRjb250aW51b3VzWzNdLCAjIFLDqWd1bGFyaXNhdGlvbiBMMiBkdSBiaWFpcyAoUmlkZ2UpDQogICAgICAgICAgICAgICAgICAgICAgICAgIGV0YSA9IDAuMTAsICMgU2hyaW5rYWdlIHBvdXIgbGUgYm9vc3RpbmcNCiAgICAgICAgICAgICAgICAgICAgICAgICAgYm9vc3RlciA9ICJnYmxpbmVhciIsICMgVHlwZSBkJ2VudHJhaW5lbWVudCA6IGxpbsOpYWlyZSBvdSBub24tbGluw6lhaXJlDQogICAgICAgICAgICAgICAgICAgICAgICAgIG9iamVjdGl2ZSA9ICJtdWx0aTpzb2Z0cHJvYiIsICMgR3JhZGllbnQvSGVzc2lhbiBwb3VyIGwnb3B0aW1pc2F0aW9uIHBhciBHcmFkaWVudCBEZXNjZW50DQogICAgICAgICAgICAgICAgICAgICAgICAgIGV2YWxfbWV0cmljID0gIm1lcnJvciIsICMgSW5leGFjdGl0dWRlIGRlIGxhIGNsYXNzaWZpY2F0aW9uIChjZXR0ZSBtw6l0cmlxdWUgZXN0IG9wdGltaXPDqWUgcGFyIHhnYm9vc3QpDQogICAgICAgICAgICAgICAgICAgICAgICAgIG1heGltaXplID0gRkFMU0UsICMgTWluaW1pc2F0aW9uIGRlIGwnZXJyZXVyDQogICAgICAgICAgICAgICAgICAgICAgICAgIGVhcmx5X3N0b3BwaW5nX3JvdW5kcyA9IDUwLCAjIEFycsOqdCBhcHLDqHMgNTAgaXTDqXJhdGlvbnMgc2FucyBhbcOpbGlvcmF0aW9uIGRlIGxhIG3DqXRyaXF1ZQ0KICAgICAgICAgICAgICAgICAgICAgICAgICB2ZXJib3NlID0gRkFMU0UsICMgU2FucyBwcmludCBkZXMgaXTDqXJhdGlvbnMNCiAgICAgICAgICAgICAgICAgICAgICAgICAgY2FsbGJhY2tzID0gbGlzdChjYi5ldmFsdWF0aW9uLmxvZygpKSkgIyBMb2dnaW5nIGRlcyBkb25uw6llcyBkJ2VudHJhaW5lbWVudCBwb3VyIHBvdXZvaXIgcsOpY3Vww6lyZXIgbGVzIG3DqXRyaXF1ZXMpDQogIA0KICAjIEVucmVnaXN0cmVtZW50IGR1IGxvZw0KICBldm9sdXRpb25bW2kgLSA5XV0gPC0gY2JpbmQodGVtcF9tb2RlbCRldmFsdWF0aW9uX2xvZywgRm9sZCA9IHJlcCgxMyAtIGksIHRlbXBfbW9kZWwkbml0ZXIpKQ0KICANCiAgIyBFbnRyYWluZW1lbnQgZHUgbWVpbGxldXIgbW9kw6hsZSAob2J0ZW50aW9uIGRlcyBtZWlsbGV1cnMgY29lZmZpY2llbnRzKQ0KICB0ZW1wX21vZGVsIDwtIHhnYi50cmFpbihkYXRhID0gdHJhaW5pbmdfeGdiW1tpXV0sDQogICAgICAgICAgICAgICAgICAgICAgICAgIHdhdGNobGlzdCA9IGxpc3QodGVzdCA9IHRlc3RpbmdfeGdiW1tpXV0pLCAjIERvbm7DqWVzIGRlIHZhbGlkYXRpb24NCiAgICAgICAgICAgICAgICAgICAgICAgICAgbnVtX2NsYXNzID0gNiwgIyA2IGNsYXNzZSBkZSBjbGFzc2lmaWNhdGlvbg0KICAgICAgICAgICAgICAgICAgICAgICAgICBudGhyZWFkID0gMSwgIyAxIHRocmVhZCBwb3VyIGxhIHJlcHJvZHVjdGlvbiBkZXMgcsOpc3VsdGF0cw0KICAgICAgICAgICAgICAgICAgICAgICAgICBucm91bmRzID0gdGVtcF9tb2RlbCRiZXN0X2l0ZXJhdGlvbiwgIyBNZWlsbGV1cmUgaXTDqXJhdGlvbg0KICAgICAgICAgICAgICAgICAgICAgICAgICBhbHBoYSA9IGJlc3Rfd2VpZ2h0cyRvcHRpbWl6ZXIkY29udGludW91c1sxXSwgIyBSw6lndWxhcmlzYXRpb24gTDEgKExhc3NvKQ0KICAgICAgICAgICAgICAgICAgICAgICAgICBsYW1iZGEgPSBiZXN0X3dlaWdodHMkb3B0aW1pemVyJGNvbnRpbnVvdXNbMl0sICMgUsOpZ3VsYXJpc2F0aW9uIEwyIChSaWRnZSkNCiAgICAgICAgICAgICAgICAgICAgICAgICAgbGFtYmRhX2JpYXMgPSBiZXN0X3dlaWdodHMkb3B0aW1pemVyJGNvbnRpbnVvdXNbM10sICMgUsOpZ3VsYXJpc2F0aW9uIEwyIGR1IGJpYWlzIChSaWRnZSkNCiAgICAgICAgICAgICAgICAgICAgICAgICAgZXRhID0gMC4xMCwgIyBTaHJpbmthZ2UgcG91ciBsZSBib29zdGluZw0KICAgICAgICAgICAgICAgICAgICAgICAgICBib29zdGVyID0gImdibGluZWFyIiwgIyBUeXBlIGQnZW50cmFpbmVtZW50IDogbGluw6lhaXJlIG91IG5vbi1saW7DqWFpcmUNCiAgICAgICAgICAgICAgICAgICAgICAgICAgb2JqZWN0aXZlID0gIm11bHRpOnNvZnRwcm9iIiwgIyBHcmFkaWVudC9IZXNzaWFuIHBvdXIgbCdvcHRpbWlzYXRpb24gcGFyIEdyYWRpZW50IERlc2NlbnQNCiAgICAgICAgICAgICAgICAgICAgICAgICAgZXZhbF9tZXRyaWMgPSAibWVycm9yIiwgIyBJbmV4YWN0aXR1ZGUgZGUgbGEgY2xhc3NpZmljYXRpb24gKGNldHRlIG3DqXRyaXF1ZSBlc3Qgb3B0aW1pc8OpZSBwYXIgeGdib29zdCkNCiAgICAgICAgICAgICAgICAgICAgICAgICAgbWF4aW1pemUgPSBGQUxTRSwgIyBNaW5pbWlzYXRpb24gZGUgbCdlcnJldXINCiAgICAgICAgICAgICAgICAgICAgICAgICAgZWFybHlfc3RvcHBpbmdfcm91bmRzID0gOTk5OTksICMgU2FucyBhcnLDqnQNCiAgICAgICAgICAgICAgICAgICAgICAgICAgdmVyYm9zZSA9IEZBTFNFLCAjIFNhbnMgcHJpbnQgZGVzIGl0w6lyYXRpb25zDQogICAgICAgICAgICAgICAgICAgICAgICAgIGNhbGxiYWNrcyA9IGxpc3QoY2IuZXZhbHVhdGlvbi5sb2coKSkpICMgTG9nZ2luZyBkZXMgZG9ubsOpZXMgZCdlbnRyYWluZW1lbnQgcG91ciBwb3V2b2lyIHLDqWN1cMOpcmVyIGxlcyBtw6l0cmlxdWVzKQ0KICANCiAgIyBQcsOpZGljdGlvbiBkdSBtb2TDqGxlIGxpbsOpYWlyZQ0KICBwcmVkaWN0ZWRWYWx1ZXNbZm9sZHNfdGVzdFtbaV1dLCBdIDwtIHQobWF0cml4KHByZWRpY3QodGVtcF9tb2RlbCwgdGVzdGluZ194Z2JbW2ldXSwgbnRyZWVsaW1pdCA9IDApLCBucm93ID0gNikpDQogIA0KICAjIENhbGN1bCBldCBmb3JtYXR0YWdlIGRlIGwnaW1wb3J0YW5jZSBkZXMgdmFyaWFibGVzDQogIHRlbXBfaW1wb3J0YW5jZSA8LSBkYXRhLnRhYmxlKEZlYXR1cmUgPSB0ZW1wX21lYW5zW1siRmVhdHVyZSJdXSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWF0cml4KHhnYi5pbXBvcnRhbmNlKG1vZGVsID0gdGVtcF9tb2RlbCkkV2VpZ2h0LCBuY29sID0gNikpDQogIGNvbG5hbWVzKHRlbXBfaW1wb3J0YW5jZSkgPC0gYygiRmVhdHVyZSIsIHBhc3RlMCgiTGFiZWxfIiwgMTo2KSkNCiAgdGVtcF9pbXBvcnRhbmNlW1siU2lnbiJdXSA8LSBwYXN0ZTAoaWZlbHNlKHRlbXBfaW1wb3J0YW5jZVtbMl1dID49IDAsICIrIiwgIi0iKSwgaWZlbHNlKHRlbXBfaW1wb3J0YW5jZVtbM11dID49IDAsICIrIiwgIi0iKSwgaWZlbHNlKHRlbXBfaW1wb3J0YW5jZVtbNF1dID49IDAsICIrIiwgIi0iKSwgaWZlbHNlKHRlbXBfaW1wb3J0YW5jZVtbNV1dID49IDAsICIrIiwgIi0iKSwgaWZlbHNlKHRlbXBfaW1wb3J0YW5jZVtbNl1dID49IDAsICIrIiwgIi0iKSwgaWZlbHNlKHRlbXBfaW1wb3J0YW5jZVtbN11dID49IDAsICIrIiwgIi0iKSkNCiAgdGVtcF9pbXBvcnRhbmNlWywgMjo3XSA8LSBhYnModGVtcF9pbXBvcnRhbmNlWywgMjo3LCB3aXRoID0gRkFMU0VdKQ0KICB0ZW1wX21lYW5zW1sxNCAtIGldXSA8LSByb3dNZWFucyh0ZW1wX2ltcG9ydGFuY2VbLCAyOjcsIHdpdGggPSBGQUxTRV0pDQogIHRlbXBfaW1wb3J0YW5jZVtbcGFzdGUwKCJGb2xkXyIsIDEzIC0gaSwgIl9NZWFuIildXSA8LSB0ZW1wX21lYW5zW1sxNCAtIGldXQ0KICANCiAgIyBFbnJlZ2lzdHJlbWVudCBzb3VzIGZvcm1lIGRlIHRhYmxlYXUgaW50ZXJhY3RpZg0KICB0ZW1wX2R0W1tpIC0gOV1dIDwtIGRhdGF0YWJsZSh0ZW1wX2ltcG9ydGFuY2UsDQogICAgICAgIGZpbHRlciA9ICJ0b3AiLCAjIEZpbHRyYWdlIGF1LWRlc3N1cyBkZSBsYSB0YWJsZQ0KICAgICAgICBjbGFzcyA9ICJjZWxsLWJvcmRlciBzdHJpcGUiLCAjIENTUw0KICAgICAgICBleHRlbnNpb25zID0gYygiQ29sUmVvcmRlciIsDQogICAgICAgICAgICAgICAgICAgICAgICJSb3dSZW9yZGVyIiksICMgUmVvcmRvbm5lciBtYW51ZWxsZW1lbnQgw6AgbGEgbWFpbg0KICAgICAgICBvcHRpb25zID0gbGlzdChwYWdlTGVuZ3RoID0gMTMsICMgUGFnZSBhZmZpY2hhbnQgMTMgbGlnbmVzDQogICAgICAgICAgICAgICAgICAgICAgIG9yZGVyID0gbGlzdChsaXN0KDksICJkZXNjIikpLCAjIE9yZG9ubmVyIHBhciBkw6lmYXV0IHBhciBsZXMgZmFjdGV1cnMgYXlhbnQgbGUgcG9pZHMgbGUgcGx1cyBncm9zDQogICAgICAgICAgICAgICAgICAgICAgIGNvbFJlb3JkZXIgPSBUUlVFLCAjIFBsdWdpbg0KICAgICAgICAgICAgICAgICAgICAgICByb3dSZW9yZGVyID0gVFJVRSkpICU+JSAjIFBsdWdpbg0KICBmb3JtYXRTdHlsZShwYXN0ZTAoIkxhYmVsXyIsIDE6NiksDQogICAgICAgICAgICAgICAgICBiYWNrZ3JvdW5kID0gc3R5bGVDb2xvckJhcihyYW5nZSh0ZW1wX2ltcG9ydGFuY2VbLCAyOjcsIHdpdGggPSBGQUxTRV0pLCAnbGlnaHRibHVlJyksICMgQ291bGV1ciBibGV1ZSBwb3VyIGxlIGNvZWZmaWNpZW50DQogICAgICAgICAgICAgICAgICBiYWNrZ3JvdW5kU2l6ZSA9ICcxMDAlIDkwJScsDQogICAgICAgICAgICAgICAgICBiYWNrZ3JvdW5kUmVwZWF0ID0gJ25vLXJlcGVhdCcsDQogICAgICAgICAgICAgICAgICBiYWNrZ3JvdW5kUG9zaXRpb24gPSAnY2VudGVyJykgJT4lDQogIGZvcm1hdFN0eWxlKHBhc3RlMCgiRm9sZF8iLCAxMyAtIGksICJfTWVhbiIpLA0KICAgICAgICAgICAgICBiYWNrZ3JvdW5kID0gc3R5bGVDb2xvckJhcihyYW5nZSh0ZW1wX2ltcG9ydGFuY2VbWzldXSksICdwaW5rJyksICMgQ291bGV1ciByb3NlIHBvdXIgbGEgbcOpdHJpcXVlIGRlIG1veWVubmUNCiAgICAgICAgICAgICAgYmFja2dyb3VuZFNpemUgPSAnMTAwJSA5MCUnLA0KICAgICAgICAgICAgICBiYWNrZ3JvdW5kUmVwZWF0ID0gJ25vLXJlcGVhdCcsDQogICAgICAgICAgICAgIGJhY2tncm91bmRQb3NpdGlvbiA9ICdjZW50ZXInKSAlPiUNCiAgZm9ybWF0Um91bmQoY29sdW1ucyA9IGMocGFzdGUwKCJMYWJlbF8iLCAxOjYpLCBwYXN0ZTAoIkZvbGRfIiwgMTMgLSBpLCAiX01lYW4iKSksDQogICAgICAgICAgICAgIGRpZ2l0cyA9IDYpDQogIA0KfQ0KDQojIENhbGN1bCBkdSBwb2lkcyBtb3llbiBhZmZlY3TDqSDDoCBjaGFxdWUgZmVhdHVyZQ0KdGVtcF9tZWFuc1tbNV1dIDwtIHJvd01lYW5zKHRlbXBfbWVhbnNbLCAyOjRdKSAjIFBvaWRzIG1veWVuDQp0ZW1wX21lYW5zW1s2XV0gPC0gYXBwbHkobWluaV9sbVssIHdoaWNoKGJlc3Rfd2VpZ2h0cyRvcHRpbWl6ZXIkZGlzY3JldGUgPT0gMSldLCAyLCBmdW5jdGlvbih4KSB7bWVhbih4KX0pICMgTW95ZW5uZSBkZSBsYSBmZWF0dXJlIGRhbnMgbGVzIGRvbm7DqWVzDQp0ZW1wX21lYW5zW1s3XV0gPC0gYXBwbHkobWluaV9sbVssIHdoaWNoKGJlc3Rfd2VpZ2h0cyRvcHRpbWl6ZXIkZGlzY3JldGUgPT0gMSldLCAyLCBmdW5jdGlvbih4KSB7c2QoeCl9KSAjIEVjYXJ0LXR5cGUgZGUgbGEgZmVhdHVyZSBkYW5zIGxlcyBkb25uw6llcw0KDQojIFByw6lwcmF0aW9uIGR1IHRhYmxlYXUgaW50ZXJhY3RpZiBzdXIgbGVzIHBvaWRzIG1veWVucyBhZ3LDqWfDqXMNCnRlbXBfZHRbWzRdXSA8LSBkYXRhdGFibGUodGVtcF9tZWFucywNCiAgICAgICAgZmlsdGVyID0gInRvcCIsICMgRmlsdHJhZ2UgYXUtZGVzc3VzIGRlIGxhIHRhYmxlDQogICAgICAgIGNsYXNzID0gImNlbGwtYm9yZGVyIHN0cmlwZSIsICMgQ1NTDQogICAgICAgIGV4dGVuc2lvbnMgPSBjKCJDb2xSZW9yZGVyIiwNCiAgICAgICAgICAgICAgICAgICAgICAgIlJvd1Jlb3JkZXIiKSwgIyBSZW9yZG9ubmVyIG1hbnVlbGxlbWVudCDDoCBsYSBtYWluDQogICAgICAgIG9wdGlvbnMgPSBsaXN0KHBhZ2VMZW5ndGggPSAxMywgIyBQYWdlIGFmZmljaGFudCAxMyBsaWduZXMNCiAgICAgICAgICAgICAgICAgICAgICAgb3JkZXIgPSBsaXN0KGxpc3QoNSwgImRlc2MiKSksICMgT3Jkb25uZXIgcGFyIGTDqWZhdXQgcGFyIGxlcyBmYWN0ZXVycyBheWFudCBsZSBwb2lkcyBsZSBwbHVzIGdyb3MgZW4gbW95ZW5uZQ0KICAgICAgICAgICAgICAgICAgICAgICBjb2xSZW9yZGVyID0gVFJVRSwgIyBQbHVnaW4NCiAgICAgICAgICAgICAgICAgICAgICAgcm93UmVvcmRlciA9IFRSVUUpKSAlPiUgIyBQbHVnaW4NCiAgZm9ybWF0U3R5bGUocGFzdGUwKCJGb2xkXyIsIDE6MyksDQogICAgICAgICAgICAgICAgICBiYWNrZ3JvdW5kID0gc3R5bGVDb2xvckJhcihyYW5nZSh0ZW1wX21lYW5zWywgMjo0XSksICdsaWdodGJsdWUnKSwgIyBDb3VsZXVyIGJsZXVlIHBvdXIgbGUgY29lZmZpY2llbnQNCiAgICAgICAgICAgICAgICAgIGJhY2tncm91bmRTaXplID0gJzEwMCUgOTAlJywNCiAgICAgICAgICAgICAgICAgIGJhY2tncm91bmRSZXBlYXQgPSAnbm8tcmVwZWF0JywNCiAgICAgICAgICAgICAgICAgIGJhY2tncm91bmRQb3NpdGlvbiA9ICdjZW50ZXInKSAlPiUNCiAgZm9ybWF0U3R5bGUoIkZvbGRfTWVhbiIsDQogICAgICAgICAgICAgIGJhY2tncm91bmQgPSBzdHlsZUNvbG9yQmFyKHJhbmdlKHRlbXBfbWVhbnNbWzVdXSksICdwaW5rJyksICMgQ291bGV1ciByb3NlIHBvdXIgbGEgbcOpdHJpcXVlIGRlIG1veWVubmUNCiAgICAgICAgICAgICAgYmFja2dyb3VuZFNpemUgPSAnMTAwJSA5MCUnLA0KICAgICAgICAgICAgICBiYWNrZ3JvdW5kUmVwZWF0ID0gJ25vLXJlcGVhdCcsDQogICAgICAgICAgICAgIGJhY2tncm91bmRQb3NpdGlvbiA9ICdjZW50ZXInKSAlPiUNCiAgZm9ybWF0U3R5bGUoIkZlYXR1cmVfTWVhbiIsDQogICAgICAgICAgICAgIGJhY2tncm91bmQgPSBzdHlsZUNvbG9yQmFyKHJhbmdlKHRlbXBfbWVhbnNbWzZdXSksICdsaWdodGdyZWVuJyksICMgQ291bGV1ciB2ZXJ0ZSBwb3VyIGxhIG1veWVubmUgZGVzIGZlYXR1cmVzDQogICAgICAgICAgICAgIGJhY2tncm91bmRTaXplID0gJzEwMCUgOTAlJywNCiAgICAgICAgICAgICAgYmFja2dyb3VuZFJlcGVhdCA9ICduby1yZXBlYXQnLA0KICAgICAgICAgICAgICBiYWNrZ3JvdW5kUG9zaXRpb24gPSAnY2VudGVyJykgJT4lDQogIGZvcm1hdFN0eWxlKCJGZWF0dXJlX1NEIiwNCiAgICAgICAgICAgICAgYmFja2dyb3VuZCA9IHN0eWxlQ29sb3JCYXIocmFuZ2UodGVtcF9tZWFuc1tbN11dKSwgJ29yYW5nZScpLCAjIENvdWxldXIgdmVydGUgcG91ciBsJ8OpY2FydC10eXBlIGRlcyBmZWF0dXJlcw0KICAgICAgICAgICAgICBiYWNrZ3JvdW5kU2l6ZSA9ICcxMDAlIDkwJScsDQogICAgICAgICAgICAgIGJhY2tncm91bmRSZXBlYXQgPSAnbm8tcmVwZWF0JywNCiAgICAgICAgICAgICAgYmFja2dyb3VuZFBvc2l0aW9uID0gJ2NlbnRlcicpICU+JQ0KICBmb3JtYXRSb3VuZChjb2x1bW5zID0gYyhwYXN0ZTAoIkZvbGRfIiwgMTozKSwgIkZvbGRfTWVhbiIsICJGZWF0dXJlX01lYW4iLCAiRmVhdHVyZV9TRCIpLA0KICAgICAgICAgICAgICBkaWdpdHMgPSA2KQ0KDQojIETDqXBpdm90YWdlIGR1IGxvZw0KZXZvbHV0aW9uIDwtIHJiaW5kbGlzdChldm9sdXRpb24pDQpjb2xuYW1lcyhldm9sdXRpb24pIDwtIGMoIkl0ZXJhdGlvbiIsICJFeGFjdGl0dWRlIiwgIkZvbGQiKQ0KZXZvbHV0aW9uJEV4YWN0aXR1ZGUgPC0gMSAtIGV2b2x1dGlvbiRFeGFjdGl0dWRlDQpldm9sdXRpb24kRm9sZCA8LSBhcy5mYWN0b3IoZXZvbHV0aW9uJEZvbGQpDQoNCiMgUHLDqWRpY3Rpb24gw6AgcGFydGlyIGRlcyBwcm9iYWJpbGl0w6lzDQpwcmVkaWN0ZWRMYWJlbCA8LSBkYXRhLmZyYW1lKExhYmVsID0gZ3JvdXBfcGF0aCRwYXRoX0lELCBQcmVkaWN0aW9uID0gYXBwbHkocHJlZGljdGVkVmFsdWVzLCAxLCBmdW5jdGlvbih4KSB7d2hpY2gubWF4KHgpfSkpDQoNCnRpbWluZyhDdXJyZW50VGltZSwgIlByw6lwYXJhdGlvbiBkZSBsJ2FuYWx5c2UgZHUgZGVybmllciBtb2TDqGxlIGRlIHLDqWdyZXNzaW9uIGxvZ2lzdGlxdWUiKQ0KDQojIEFmZmljaGFnZSBkZSBsJ8Opdm9sdXRpb24gZGUgbGEgcGVyZm9ybWFuY2UgZHUgbW9kw6hsZSBzZWxvbiBsZSBub21icmUgZCdpdMOpcmF0aW9uLCBzb3VzIGZvcm1lIGRlIHBsb3QgaW50ZXJhY3RpZg0KZ2dwbG90bHkoZ2dwbG90KGRhdGEgPSBldm9sdXRpb24sIGFlc19zdHJpbmcoeCA9ICJJdGVyYXRpb24iLCB5ID0gIkV4YWN0aXR1ZGUiLCBncm91cCA9ICJGb2xkIiwgY29sb3IgPSAiRm9sZCIpKSArIGdlb21fbGluZSgpICsgZ2VvbV9wb2ludCgpICsgc2NhbGVfY29sb3JfYnJld2VyKHBhbGV0dGUgPSAiU2V0MiIpICsgdGhlbWVfYncoKSArIGxhYnModGl0bGUgPSAiRXZvbHV0aW9uIGRlIGwnZXhhY3RpdHVkZSBwYXIgcmFwcG9ydCBhdSBub21icmUgZCdpdMOpcmF0aW9ucyBkJ2VudHJhaW5lbWVudCIpLCB3aWR0aCA9IDk2MCwgaGVpZ2h0ID0gNzIwKQ0KDQojIEFmZmljaGFnZSBkZSBsYSBtYXRyaWNlIGRlIGNvbmZ1c2lvbiBzb3VzIGZvcm1lIGRlIHBsb3QgaW50ZXJhY3RpZg0KY29uZnVzaW9uX21hdCA8LSBleHBhbmQuZ3JpZChMYWJlbCA9IDE6NiwgUHJlZGljdGlvbiA9IDE6NikNCmNvbmZ1c2lvbl9tYXQgPC0gbWVyZ2UoY29uZnVzaW9uX21hdCwgZGF0YS50YWJsZShwcmVkaWN0ZWRMYWJlbClbLCBsaXN0KEZyZXEgPSBzdW0oLk4pKSwgYnkgPSBsaXN0KExhYmVsLCBQcmVkaWN0aW9uKV0sIGJ5ID0gYygiTGFiZWwiLCAiUHJlZGljdGlvbiIpLCBhbGwueCA9IFRSVUUpDQpjb25mdXNpb25fbWF0W1siRnJlcSJdXVtpcy5uYShjb25mdXNpb25fbWF0W1siRnJlcSJdXSldIDwtIDANCmNvbmZ1c2lvbl9tYXRbWyJMYWJlbCJdXSA8LSBhcy5mYWN0b3IoY29uZnVzaW9uX21hdFtbIkxhYmVsIl1dKQ0KY29uZnVzaW9uX21hdFtbIlByZWRpY3Rpb24iXV0gPC0gYXMuZmFjdG9yKGNvbmZ1c2lvbl9tYXRbWyJQcmVkaWN0aW9uIl1dKQ0KZ2dwbG90bHkoZ2dwbG90KCkgKyBnZW9tX3JlY3QoZGF0YSA9IGRhdGEuZnJhbWUoY2VudCA9IDE6NiksIHNpemUgPSAyLCBmaWxsID0gTkEsIGNvbG91ciA9ICJibGFjayIsIGFlcyh4bWluID0gY2VudCAtIDAuNSwgeG1heCA9IGNlbnQgKyAwLjUsIHltaW4gPSBjZW50IC0gMC41LCB5bWF4ID0gY2VudCArIDAuNSkpICsgZ2VvbV90aWxlKGRhdGEgPSBjb25mdXNpb25fbWF0LCBhZXNfc3RyaW5nKHggPSAiTGFiZWwiLCB5ID0gIlByZWRpY3Rpb24iLCBmaWxsID0gIkZyZXEiKSkgKyBnZW9tX3RleHQoZGF0YSA9IGNvbmZ1c2lvbl9tYXQsIGFlc19zdHJpbmcoeCA9ICJMYWJlbCIsIHkgPSAiUHJlZGljdGlvbiIsIGxhYmVsID0gIkZyZXEiKSkgKyBzY2FsZV94X2Rpc2NyZXRlKG5hbWUgPSAiVHJhamVjdG9pcmUgUsOpZWxsZSIpICsgc2NhbGVfeV9kaXNjcmV0ZShuYW1lID0gIlRyYWplY3RvaXJlIFByw6lkaXRlIikgKyBzY2FsZV9maWxsX2dyYWRpZW50bihjb2xvdXJzID0gcmV2KGJyZXdlci5wYWxfZXh0ZW5kZWQoMywgIlBpWUciKSkpICsgbGFicyh0aXRsZSA9ICJNYXRyaWNlIGRlIENvbmZ1c2lvbiBkZSBsYSBUcmFqZWN0b2lyZSIsIGZpbGwgPSAiRnLDqXF1ZW5jZSIpLCB3aWR0aCA9IDk2MCwgaGVpZ2h0ID0gNzIwKQ0KDQojIEFmZmljaGFnZSBkZXMgdGFibGVzIMOgIGxhIGZpbiBjYXIgbGUgZm9ybWF0dGFnZSBwb3Nzw6hkZSB1biBidWcgaW5ow6lyZW50IGxvcnNxdSdvbiBhIHBsdXNpZXVycyBkYXRhdGFibGVzIChEVCkgZGFucyBsZSBtw6ptZSBjaHVuaw0KIyBodG1sdG9vbHM6OnRhZ0xpc3QodGVtcF9kdFtbM11dLCB0ZW1wX2R0W1syXV0sIHRlbXBfZHRbWzFdXSwgdGVtcF9kdFtbNF1dKQ0KYGBgDQoNCmBgYHtyIFRlbXA5LCBlY2hvPUZBTFNFfQ0KdGVtcF9kdFtbM11dICMgQ29lZmZpY2llbnRzIGNvbnRyZSBsYSBzYWxsZSAzDQpgYGANCg0KYGBge3IgVGVtcDEwLCBlY2hvPUZBTFNFfQ0KdGVtcF9kdFtbMl1dICMgQ29lZmZpY2llbnRzIGNvbnRyZSBsYSBzYWxsZSAyDQpgYGANCg0KYGBge3IgVGVtcDExLCBlY2hvPUZBTFNFfQ0KdGVtcF9kdFtbMV1dICMgQ29lZmZpY2llbnRzIGNvbnRyZSBsYSBzYWxsZSAxDQpgYGANCg0KYGBge3IgVGVtcDEyLCBlY2hvPUZBTFNFfQ0KdGVtcF9kdFtbNF1dICMgQ29lZmZpY2llbnRzIGFncsOpZ8Opcw0KYGBgDQoNCiMgQ29uY2x1c2lvbg0KDQpOb3VzIGF2b25zIGZpbmkgbGUgZMOpYnV0IGRlIHRhY2hlIGQnYW5hbHlzZSBhcHLDqHMgNiBoZXVyZXMgZGUgdHJhdmFpbC4gQ2Ugbidlc3QgcXUndW4gZMOpYnV0IGQnZXhwbG9yYXRpb24sIGV0IGxlcyBwZXJmb3JtYW5jZXMgcGV1dmVudCDDqnRyZSBiaWVuIG1laWxsZXVyZXMgcGFyIGxhIGNyw6lhdGlvbiBkZSBmZWF0dXJlcyBkaWZmw6lyZW50ZXMuIE5vdXMgYXVyaW9ucyBwdSBwYXIgZXhlbXBsZSBmb3JjZXIgZGl2ZXJzZXMgaW50ZXJhY3Rpb25zIGVudHJlIGxlcyB2YXJpYWJsZXMgcGFyIGRlcyBtdWx0aXBsaWNhdGlvbnMsIG91IGVuY29yZSBjcsOpw6kgZGVzIGZlYXR1cmVzIGQndW5lIGF1dHJlIG1hbmnDqHJlIChleGVtcGxlcyA6IHByw6lkaWN0aW9uIGRlcyBwcm9jaGFpbnMgcG9pbnRzIHBhciBzw6lyaWUgdGVtcG9yZWxsZSwgdXRpbGlzYXRpb24gdW5pcXVlbWVudCBkJ3VuIG5vbWJyZSByZXN0cmVpbnQgZGUgcG9pbnRzIHBvdXIgbGUgY2FsY3VsIGRlcyBmZWF0dXJlcywgdXRpbGlzYXRpb24gZGVzIFggZGVybmllcnMgcG9pbnRzIGNvbW1lIGZlYXR1cmVzLi4uKS4NCg0KQ29tbWUgcHLDqXZ1IGR1cmFudCBsYSBzZWNvbmRlIGFuYWx5c2UgZXhwbG9yYXRvaXJlIGF2ZWMgbGEgZMOpbW9uc3RyYXRpb24gZHUgcHJvYmzDqG1lIGRlIGRvbWFpbmVzIGRlIGTDqWZpbml0aW9uIGRlcyB2YXJpYWJsZXMgKHByb2Jsw6htZSBkZSBsaW7DqWFyaXTDqSksIGxlcyBtb2TDqGxlcyBsaW7DqWFpcmVzIHMnZW4gc29ydGVudCBsZXMgbWVpbGxldXJzIMOgIGxhIGZpbi4gTGVzIG1vZMOobGVzIG5vbi1saW7DqWFpcmVzIHNvbnQgw6AgbGEgdHJhaW5lIGVuIHBlcmZvcm1hbmNlLCB2dSBxdWUgOg0KDQotIFBvdXIgdG91dCBwcm9ibMOobWUgZGUgbW9kw6lsaXNhdGlvbiwgaWwgZXN0IHBsdXMgZmFjaWxlIGQnYXBwcmVuZHJlIHVuIHByb2Jsw6htZSBsaW7DqWFpcmUgcXUndW4gcHJvYmzDqG1lIG5vbi1saW7DqWFpcmUNCi0gUG91ciB0b3V0IHByb2Jsw6htZSBkZSBtb2TDqWxpc2F0aW9uLCBzaSBsZSBwcm9ibMOobWUgc2UgcmFwcHJvY2hlIGQndW4gcHJvYmzDqG1lIGxpbsOpYWlyZSwgYWxvcnMgbGEgcGVyZm9ybWFuY2UgZCd1biBtb2TDqGxlIGxpbsOpYWlyZSBzZXJhIHN1cMOpcmlldXJlIMOgIGxhIHBlcmZvcm1hbmNlIGQndW4gbW9kw6hsZSBub24tbGluw6lhaXJlIGRhbnMgbGEgcGx1cGFydCBkZXMgY2FzDQoNCkRlIGZhaXQsIGwnaW1wb3J0YW50IGVzdCBkZSByZXRlbmlyIHF1ZSBsYSBwZXJmb3JtYW5jZSBkdSBtb2TDqGxlIGTDqXBlbmQgZGFucyBsJ29yZHJlIGRlIHByaW9yaXTDqSA6DQoNCi0gTGEgcGx1cyBpbXBvcnRhbnRlIDogbGVzIGZlYXR1cmVzDQotIFBldSBpbXBvcnRhbnQgOiBsZXMgaHlwZXJwYXJhbcOodHJlcw0KDQpWb2ljaSBsZSBzb21tYWlyZSBkZXMgcsOpc3VsdGF0cyBsb3JzcXUnb24gYSBlbnRyYWluw6kgYXZlYyBkZXV4IGRhdGFzZXRzIGNvbnRyZSB1bmUgZGF0YXNldCA6DQoNCmBgYHtyIEFncmVnYXRpb25TY29yZXN9DQojIENoYXJnZW1lbnQgZGVzIHNjb3JlcyBlbnJlZ2lzdHLDqXMgYXZlYyBuZXR0b3lhZ2UNCmFjY3VyYWN5MSA8LSB0KGZyZWFkKCJzY29yZXMvMV9tb2RlbHMuY3N2IilbMTUsIC0xXVsxLCBjKDEsIDUsIDIsIDYsIDMsIDcsIDQsIDgsIDk6MTIpXSkNCmFjY3VyYWN5MiA8LSB0KGZyZWFkKCJzY29yZXMvMl9tb2RlbHMuY3N2IilbMTUsIC0xXVsxLCBjKDEsIDUsIDIsIDYsIDMsIDcsIDQsIDgsIDk6MTIpXSkNCmFjY3VyYWN5MyA8LSB0KGZyZWFkKCJzY29yZXMvM19tb2RlbHMuY3N2IilbMTUsIC0xXVsxLCBjKDEsIDUsIDIsIDYsIDMsIDcsIDQsIDgsIDk6MTIpXSkNCmFjY3VyYWN5NCA8LSB0KGZyZWFkKCJzY29yZXMvNF9tb2RlbHMuY3N2IilbMTUsIC0xXVsxLCBjKDEsIDUsIDIsIDYsIDMsIDcsIDQsIDgsIDk6MTIpXSkNCg0KIyBBZ3LDqWdhdGlvbiBkZXMgc2NvcmVzDQphY2N1cmFjeV9hZ2cgPC0gZGF0YS50YWJsZShEZWZhdXQgPSBjKGFjY3VyYWN5MVsxOjJdLCBtZWFuKGFjY3VyYWN5MVs5OjEwXSksIG1lYW4oYWNjdXJhY3kxWzExOjEyXSksIGFjY3VyYWN5MVszOjhdKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgIEFwcHJveGltYXRpb24gPSBjKGFjY3VyYWN5MlsxOjJdLCBtZWFuKGFjY3VyYWN5Mls5OjEwXSksIG1lYW4oYWNjdXJhY3kyWzExOjEyXSksIGFjY3VyYWN5MlszOjhdKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgIEludmVyc2VtZW50ID0gYyhhY2N1cmFjeTNbMToyXSwgbWVhbihhY2N1cmFjeTNbOToxMF0pLCBtZWFuKGFjY3VyYWN5M1sxMToxMl0pLCBhY2N1cmFjeTNbMzo4XSksDQogICAgICAgICAgICAgICAgICAgICAgICAgICBTZWxlY3Rpb24gPSBjKGFjY3VyYWN5NFsxOjJdLCBtZWFuKGFjY3VyYWN5NFs5OjEwXSksIG1lYW4oYWNjdXJhY3k0WzExOjEyXSksIGFjY3VyYWN5NFszOjhdKSkNCmFjY3VyYWN5X2FnZ1tbIkV2b2x1dGlvbiJdXSA8LSBhcHBseShhY2N1cmFjeV9hZ2csIDEsIGZ1bmN0aW9uKHgpIHttYXgoeCkgLSBtaW4oeCl9KQ0Kcm93Lm5hbWVzKGFjY3VyYWN5X2FnZykgPC0gYygiUsOpZ3Jlc3Npb24gbG9naXN0aXF1ZSAoeGdiKSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICJSw6lncmVzc2lvbiBsb2dpc3RpcXVlIChoMm8pIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIlLDqXNlYXUgZGUgbmV1cm9uZXMgMzJ4NiAoaDJvKSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICJSw6lzZWF1IGRlIG5ldXJvbmVzIDE2eDE2eDYgKGgybykiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiQXJicmUgZGUgZMOpY2lzaW9uICh4Z2IpIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIkFyYnJlIGRlIGTDqWNpc2lvbiAoaDJvKSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICJGb3LDqnQgYWzDqWF0b2lyZSAoeGdiKSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICJGb3LDqnQgYWzDqWF0b2lyZSAoaDJvKSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICJBcmJyZXMgYm9vc3TDqXMgKHhnYikiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiQXJicmVzIGJvb3N0w6lzIChoMm8pIikNCg0KIyBBZmZpY2hhZ2UgZGVzIHNjb3JlcyBkYW5zIHVuIHRhYmxlYXUgaW50ZXJhY3RpZg0KZGF0YXRhYmxlKGFjY3VyYWN5X2FnZywNCiAgICAgICAgICBmaWx0ZXIgPSAidG9wIiwgIyBGaWx0cmFnZSBhdS1kZXNzdXMgZGUgbGEgdGFibGUNCiAgICAgICAgICBjbGFzcyA9ICJjZWxsLWJvcmRlciBzdHJpcGUiLCAjIENTUw0KICAgICAgICAgIGV4dGVuc2lvbnMgPSBjKCJDb2xSZW9yZGVyIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAiUm93UmVvcmRlciIpLCAjIFJlb3Jkb25uZXIgbWFudWVsbGVtZW50IMOgIGxhIG1haW4NCiAgICAgICAgICBvcHRpb25zID0gbGlzdChwYWdlTGVuZ3RoID0gMTAsICMgUGFnZSBhZmZpY2hhbnQgMTAgbGlnbmVzDQogICAgICAgICAgICAgICAgICAgICAgICAgY29sUmVvcmRlciA9IFRSVUUsICMgUGx1Z2luDQogICAgICAgICAgICAgICAgICAgICAgICAgcm93UmVvcmRlciA9IFRSVUUpKSAlPiUgIyBQbHVnaW4NCiAgZm9ybWF0U3R5bGUoYygiRGVmYXV0IiwgIkFwcHJveGltYXRpb24iLCAiSW52ZXJzZW1lbnQiLCAiU2VsZWN0aW9uIiksDQogICAgICAgICAgICAgICAgICBiYWNrZ3JvdW5kID0gc3R5bGVDb2xvckJhcihjKDAsIG1heChhY2N1cmFjeV9hZ2dbWyJTZWxlY3Rpb24iXV0pKSwgJ2xpZ2h0Z3JlZW4nKSwgIyBDb3VsZXVyIHZlcnQgY2xhaXIgcG91ciBsZXMgbcOpdHJpcXVlcyBwYXIgZm9sZA0KICAgICAgICAgICAgICAgICAgYmFja2dyb3VuZFNpemUgPSAnMTAwJSA5MCUnLA0KICAgICAgICAgICAgICAgICAgYmFja2dyb3VuZFJlcGVhdCA9ICduby1yZXBlYXQnLA0KICAgICAgICAgICAgICAgICAgYmFja2dyb3VuZFBvc2l0aW9uID0gJ2NlbnRlcicpICU+JQ0KICBmb3JtYXRTdHlsZShjKCJFdm9sdXRpb24iKSwNCiAgICAgICAgICAgICAgICAgIGJhY2tncm91bmQgPSBzdHlsZUNvbG9yQmFyKGMoMCwgbWF4KGFjY3VyYWN5X2FnZ1tbIkV2b2x1dGlvbiJdXSkpLCAncGluaycpLCAjIENvdWxldXIgcm9zZSBwb3VyIGwnw6l2b2x1dGlvbiBkZSBsYSBtw6l0cmlxdWUNCiAgICAgICAgICAgICAgICAgIGJhY2tncm91bmRTaXplID0gJzEwMCUgOTAlJywNCiAgICAgICAgICAgICAgICAgIGJhY2tncm91bmRSZXBlYXQgPSAnbm8tcmVwZWF0JywNCiAgICAgICAgICAgICAgICAgIGJhY2tncm91bmRQb3NpdGlvbiA9ICdjZW50ZXInKSAlPiUNCiAgZm9ybWF0UGVyY2VudGFnZShjb2x1bW5zID0gYygiRGVmYXV0IiwgIkFwcHJveGltYXRpb24iLCAiSW52ZXJzZW1lbnQiLCAiU2VsZWN0aW9uIiwgIkV2b2x1dGlvbiIpLA0KICAgICAgICAgICAgICBkaWdpdHMgPSA0KQ0KYGBgDQoNCkVuIGVmZmV0LCBvbiBuZSBwZXV0IHBhcyBmYWlyZSBhcHByZW5kcmUgw6AgdW4gYWxnb3JpdGhtZSBjb21tZW50IGNyw6llciB1bmUgZmVhdHVyZSBxdWkgbidleGlzdGUgcGFzIGVuIHRhbnQgcXUnZW50csOpZS4NCg0KSWwgZXN0IHRvdXQgw6AgZmFpdCBwb3NzaWJsZSBxdSd1biByw6lzZWF1IGRlIG5ldXJvbmVzIGF2ZWMgdW5lIGFyY2hpdGVjdHVyZSBwbHVzIGF2YW5jw6llIHB1aXNzZSBmYWlyZSBiZWF1Y291cCBtaWV1eCwgdW5pcXVlbWVudCBlbiB1dGlsaXNhbnQgbGVzIGZlYXR1cmVzIGluaXRpYWxlcy4gQydlc3QgY2UgcXVlIEJhY2NpdSBldCBhbC4gb250IHLDqWFsaXPDqSBkYW5zICpBbiBleHBlcmltZW50YWwgY2hhcmFjdGVyaXphdGlvbiBvZiByZXNlcnZvaXIgY29tcHV0aW5nIGluIGFtYmllbnQgYXNzaXN0ZWQgbGl2aW5nIGFwcGxpY2F0aW9ucyogc3DDqWNpZmlxdWVtZW50IHBvdXIgcHLDqWRpcmUgbGUgY2hhbmdlbWVudCBkZSB6b25lIGRhbnMgdW5lIHNhbGxlIChqdXNxdSfDoCA5OSUgZCdleGFjdGl0dWRlKS4=